Skip to main content

Publish a single object (partial publish)

caution

Partial publish is not in the public API yet. The endpoint is implemented but gated per tenant, so it is absent from the published API reference. Confirm it is enabled on your tenant before relying on it.

When automation changes one object, you usually want to publish only that object, not whatever else happens to be sitting in the shared draft. Partial publish makes this a clean two-call pattern: edit the object, then publish only that object by ID.

Use this when:

  • A script makes frequent, narrow changes (add a user to a group, add an application) and must not ship unrelated draft edits.
  • Multiple processes or an administrator share the tenant draft.

Prerequisites: a Super User service account and the environment variables from Getting started. Read Partial publish and Draft and publish first.

note

Non-policy objects only. This phase supports user groups (0UG), application groups (0AG), applications (0AP), device groups (0DG), and tags (0TG). Naming a rule or section returns 501. For policy changes, use a normal full publish.


The two-call pattern

1. Edit the object

For example, add a user to a group:

export UG_ID='0UG01TEAMXXXXXXXXXXXXXXXXXXXX'

curl -sS -X PUT "$PB_API_BASE/user-groups/$UG_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "users": [ { "userId": "0UR01NEWXXXXXXXXXXXXXXXXXXXXX", "action": "add" } ] }'

Response (200):

{ "id": "0UG01TEAMXXXXXXXXXXXXXXXXXXXX", "userGroupId": "0UG01TEAMXXXXXXXXXXXXXXXXXXXX" }

The id you need for the next call comes back in this response, so no extra lookup is required.

2. Publish only that object

curl -sS -X POST "$PB_API_BASE/configuration-management/draft/partial-publish" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "entityIds": ["'"$UG_ID"'"], "description": "ServiceNow INC0042311: contractor offboarding" }'

Response (201):

{
"configurationVersion": { "id": "0CV01EXAMPLEXXXXXXXXXXXXXXXXX", "number": 73 },
"publishedEntityIds": ["0UG01TEAMXXXXXXXXXXXXXXXXXXXX"]
}

Everything else still pending in the draft stays pending. Only the group you named went live.

Creating and deleting objects follow the same pattern: perform the write, then publish the id it returned.

note

Put the originating ticket, request, or event reference in description. It is stored with the configuration version and shown as the change reason in the configuration log, which lets you trace any published change back to what triggered it. Where no ticket reference exists, use a stable identifier for the automation and its run, for example "Identity sync job 2026-08-06T02:00Z".


Publish several objects at once

Where one automation run modifies several objects, collect the IDs and submit them in a single request rather than one call per object. The publish is atomic: all named entities are promoted, or none are.

curl -sS -X POST "$PB_API_BASE/configuration-management/draft/partial-publish" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entityIds": [
"0UG01TEAMXXXXXXXXXXXXXXXXXXXX",
"0AG01DESIGNXXXXXXXXXXXXXXXXXX"
],
"description": "ServiceNow INC0042311: contractor offboarding"
}'

entityIds takes 1 to 1000 unique IDs. The object type comes from the ID prefix, so no type parameter is needed.


Check what is pending before you publish

To see what your process (or others) have staged, list pending changes:

curl -sS "$PB_API_BASE/configuration-management/draft/pending-changes" \
-H "Authorization: Bearer $PB_TOKEN"

Response (200):

{
"pageInfo": { "hasNextPage": false, "cursor": "", "totalCount": 1 },
"data": [
{
"entityId": "0UG01TEAMXXXXXXXXXXXXXXXXXXXX",
"entityType": "userGroup",
"name": "Team group",
"lastUpdatedBy": "0UR01EXAMPLEADMINXXXXXXXXXXXX",
"operation": "updated"
}
// ... more pending changes
]
}

Each row reports entityId, entityType, name, lastUpdatedBy, and operation (created / updated / deleted). Filter by lastUpdatedBy client-side to see only your own edits. An empty draft returns 200 with an empty list.

note

In a multi-writer environment, publish by the exact IDs you changed rather than doing a full publish. This is the safest way to avoid shipping a colleague's half-finished edit.


Full script (Python)

import os, requests

base = os.environ["PB_API_BASE"]
headers = {"Authorization": f"Bearer {os.environ['PB_TOKEN']}"}

def edit_and_publish_group(ug_id, add=(), remove=(), reason=None):
# 1. Edit the object
users = [{"userId": u, "action": "add"} for u in add] + \
[{"userId": u, "action": "remove"} for u in remove]
if users:
requests.put(f"{base}/user-groups/{ug_id}", headers=headers,
json={"users": users}, timeout=30).raise_for_status()

# 2. Publish only that object
resp = requests.post(f"{base}/configuration-management/draft/partial-publish", headers=headers,
json={"entityIds": [ug_id], "description": reason or f"Update {ug_id}"}, timeout=30)
resp.raise_for_status()
return resp.json()

result = edit_and_publish_group("0UG01TEAMXXXXXXXXXXXXXXXXXXXX",
add=["0UR01NEWXXXXXXXXXXXXXXXXXXXXX"],
reason="ServiceNow INC0042311: contractor offboarding")
print("published:", result["publishedEntityIds"], "version:", result["configurationVersion"]["number"])

Error cases

Errors share one envelope. Where present, details[] identifies the offending items, and field gives the position in the request, for example entityIds[2]:

{
"error": {
"code": "NOT_FOUND",
"message": "No pending change found for one or more of the specified entities.",
"timestamp": "2026-08-06T09:15:00Z",
"details": []
}
}
HTTPcodeConditionWhat to do
400VALIDATION_ERRORAn ID is malformed or duplicated, the list is empty or exceeds 1000 items, or the body carries only descriptionCorrect the request. This is a defect in the caller; do not retry unchanged
403FORBIDDENThe service account cannot publish configuration changesUse a Super User account
404NOT_FOUNDA valid, supported ID has no pending change in the draft. details[] lists every affected IDUsually the write made no material change, or the object was already published. Do not retry unchanged
409CONFLICTA named object depends on an unpublished change you did not include. details[].ids lists the blockersInclude the blocking IDs and retry, or escalate. Reports the first conflict found
500INTERNAL_ERRORUnexpected server-side failureRetry with bounded exponential backoff. Nothing was published
501PARTIAL_PUBLISH_POLICY_UNSUPPORTEDYou named a rule or section (0RL / 0SR)Publish policy changes with a full publish
501UNSUPPORTED_OPERATIONThe entity type is not partially publishable, or partial publish is not enabled for the tenantUse a full publish

Two of these deserve explicit handling:

  • 404 after an apparently successful write. A PUT that changes nothing, such as adding a user who is already a member or removing one who is not, stages no pending change for the publish to promote. Decide whether your automation treats this as success or as an exception.
  • 409 from an unpublished dependency. When an administrator has an unpublished rule that references the object you are publishing, both must be published together. This most often appears when deleting an object a rule still uses. Escalation is usually more appropriate than an automated retry.