Data API — Agent Management
Create, read, edit and delete agents programmatically: profile, licenses, skill levels and default outbound caller ID.
The Agent Management APIs let an external system provision and maintain CCaaS agents without the web interface — creating agents, assigning licenses and skills, editing profiles, setting a default outbound caller ID, and deleting agents, one at a time or up to 500 per request. Every write follows the same rules the web interface's agent editor applies, and every change is recorded in the settings audit log against the Service User that made it.
Good-to-Knows
FQDN(Fully Qualified Domain Name) refers to the CCaaS organizational hostname, domain, and top-level domain. Typically formatted as:[hostname].xima.cloud.- For rate limiting rules and the status codes shared by all CCaaS APIs, see API Limits & Statuses.
Getting Started
Every request is sent to the tenant's /rest base path and authenticated with a Service User bearer token.
Base URL
https://<hostname>.xima.cloud/rest/api/v1/agents
Replace <hostname> with the tenant's hostname. The /rest prefix is required and is easy to leave out.
Authentication
Every request requires a Service User token in the Authorization header:
Authorization: Bearer <api_bearer_token>
Service Users are created under API Enablement > API Service Users, where the token is copied from the Service User's details. See API Service Users.
Writes Require a Service User With Full AccessAny valid token can read agents. Creating, editing and deleting agents and setting a default outbound caller ID require a Service User whose access is unrestricted; a restricted one receives
403. Every change is recorded in the settings audit log against the Service User that made it.
Corporate Central Repository Tokens Are Read-OnlyA corporate central repository token can read agents but not write them. On a child tenant it is given full access whatever its Service User may do on the corporate tenant, so until that is fixed for every API, agent writes refuse it with
403 EPHEMERAL_SERVICE_USER_READ_ONLY, dry runs included. Writes require a Service User created on the tenant being written to.
Content Type
All request bodies are JSON. Requests must send Content-Type: application/json.
First Request
The simplest request lists the tenant's licensed agents:
curl -s "https://<hostname>.xima.cloud/rest/api/v1/agents" \
-H "Authorization: Bearer $TOKEN"Agent Types
Every agent is one of two types, which determines what the API can do with it.
| Type | Description | Supported Operations |
|---|---|---|
XIMA | A browser (WebRTC) agent created in Xima | Create, read, edit and delete everything |
UC | An agent that comes from the phone system | Read; edit licenses, skill levels and default outbound caller ID. Cannot be created or deleted, and its profile cannot be edited — those belong to the phone system |
How Responses Work
There are two kinds of failure, and telling them apart matters: a request-level failure means nothing was written, while an item-level failure on a bulk endpoint returns 200 with only some agents written.
Request-Level Failures
A request-level failure returns an HTTP error status. The whole request was refused and nothing was written. The body is an RFC 9457 problem document (Content-Type: application/problem+json):
{
"type": "https://guide.xima.cloud/docs/api-enablement-data-apis-agent-management#conflict",
"title": "Conflict",
"status": 409,
"detail": "Extension 101 is already used by another agent",
"errors": [
{ "facet": "profile", "field": "profile.extension", "reason": "EXTENSION_IN_USE",
"message": "Extension 101 is already used by another agent" }
],
"dryRun": false,
"results": [ { "index": 0, "status": "REJECTED", "…": "…" } ],
"summary": { "created": 0, "updated": 0, "deleted": 0, "rejected": 1, "valid": 0, "withFailures": 0 }
}
Best PracticeIntegrations should branch on
errors[].reason, never onmessageordetail. Reasons are a fixed list (see Errors); messages are written for people and can change.
A single-agent write refused because of the agent itself (a 400, 404 or 409 about its fields, licenses, skills, caller ID or identity) also carries the full results body, showing which part of the agent was refused. A refusal of the request as a whole (403, 413, 429, 502, 503, or a body or query parameter that cannot be read) carries errors only.
Some refusals use an older error shape, { "exception": "…", "message": "…" }, instead of a problem document:
401, and the403for a Service User without full access, which come from the shared authentication layer. The403for a corporate central repository token is a problem document like any other refusal (see service-user-read-only).415for aContent-Typeother thanapplication/json,405for a method the path does not take, and406for anAcceptheader that rules out JSON. These are made before the request reaches the agent API.- Every response from the two older endpoints, the agent list and
…/outbound-caller-ids, which do not use problem documents at all. An unknown?typeon the list, for example, is a400in the older shape.
A query parameter of the wrong type (?dryRun=maybe) is 400 MALFORMED_BODY.
Item-Level Failures on the Bulk Endpoints
On the bulk endpoints, an item-level failure returns HTTP 200, with status: "REJECTED" on the affected item. The request was understood and processed, but one or more agents in it could not be written. A bulk request where three of four agents succeed is a successful request with one rejected item — not an error.
This applies to the bulk endpoints only. A single-agent create or edit that cannot be written fails the request with its 400, 404 or 409 — there is no partial success to report when there was only ever one agent.
{
"dryRun": false,
"results": [
{ "index": 0, "id": "53436b64-…", "agent": "Jane Doe(101)", "type": "XIMA", "status": "CREATED",
"facets": { "profile": { "status": "APPLIED" }, "licenses": { "status": "APPLIED" },
"skillLevels": { "status": "NOT_REQUESTED" },
"defaultOutboundCallerId": { "status": "NOT_REQUESTED" },
"welcomeEmail": { "status": "APPLIED" } },
"warnings": [], "errors": [], "failures": [] },
{ "index": 1, "agent": "John Roe(102)", "status": "REJECTED",
"facets": { "profile": { "status": "NOT_APPLIED" },
"licenses": { "status": "REJECTED", "errors": [ { "facet": "licenses", "field": "licenses",
"reason": "SEAT_LIMIT_REACHED", "message": "…",
"details": { "license": "CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON", "count": 51, "limit": 50 } } ] },
"skillLevels": { "status": "NOT_REQUESTED" },
"defaultOutboundCallerId": { "status": "NOT_REQUESTED" },
"welcomeEmail": { "status": "NOT_APPLIED" } },
"warnings": [],
"errors": [ { "facet": "licenses", "field": "licenses", "reason": "SEAT_LIMIT_REACHED", "message": "…" } ],
"failures": [] }
],
"summary": { "created": 1, "updated": 0, "deleted": 0, "rejected": 1, "valid": 0, "withFailures": 0 }
}
A 200 Does Not Mean Every Agent Was WrittenOn the bulk endpoints, integrations must always check
summary.rejected,summary.withFailuresand each result'sstatus.
Facets
An agent is several pieces of configuration, and each write reports on each piece separately under facets:
| Facet | Description |
|---|---|
profile | Name, extension, email, recording mode and permissions, connected numbers, no-answer overflow |
licenses | The agent's licenses |
skillLevels | The agent's skill memberships and agent-wide skill settings |
defaultOutboundCallerId | The caller ID the agent dials out with by default |
welcomeEmail | The welcome email sent to a new agent |
Facet Statuses
Each facet reports a status:
| Status | Description |
|---|---|
APPLIED | Written |
DEFAULTED | licenses on a create that named none: the starter licenses a new agent gets in the web interface |
VALID | Dry run only: would have been written |
REJECTED | Refused, so nothing in the agent was written. errors says why |
NOT_APPLIED | Another part of the agent was refused, so this part was not written either |
FAILED | Ran after the agent was saved and failed. The rest of the agent stands. See After-Save Failures |
NOT_REQUESTED | The request did not ask to change it |
profile, licenses, skillLevels and defaultOutboundCallerId are decided together: for each agent, either everything requested is written or nothing is. If an agent's licenses are refused, its profile, skill levels and caller ID are not written either. Other agents in the same request are unaffected.
Result Statuses
Each result in results reports the outcome for one agent:
| Status | Description |
|---|---|
CREATED | A new agent was created |
UPDATED | An existing agent was changed |
DELETED | The agent was deleted |
REJECTED | Nothing was written for this agent. See errors |
VALID | Dry run only: this agent would have been written |
There is no partial status. A saved agent whose after-save step failed is CREATED or UPDATED with an entry in failures (see After-Save Failures).
id is present exactly when the agent exists after the request. A rejected create has no id, nor does a dry-run create: the id is assigned when the agent is saved. UC agents never have one.
Warnings
A warning means the write happened, and something in the request was not applied. Every write result carries a warnings[] array.
{
"field": "profile.recordingMod",
"action": "IGNORED",
"reason": "Not a recognised profile field. Check the spelling against GET /api/v1/agents/{id}."
}Agent writes report IGNORED warnings for:
- A field name that is not recognized, at the top level of an agent, anywhere in
profile(insideconnectedNumbers[]andnoAnswerOverflowtoo), in a skill entry or inskillSettings - A read-only field (
agent,name,extension,type,email) sent on an edit with a value that differs from the stored one profile.idon a create (the server assigns it), andsendWelcomeEmailon an edit
Log Every WarningWarnings are how typos are caught. A misspelled field name is not an error — it is simply not applied — so
IGNOREDwarnings are the only signal that the request contained something the API did not understand. Integrations should log them.
The bulk request body itself is stricter. Its only keys are dryRun and agents; any other key refuses the whole request with 400 MALFORMED_BODY and nothing written, because a misspelled dryRun would otherwise turn a dry run into a real write. Unknown fields inside each agent still get IGNORED warnings.
Endpoints
The Agent Management APIs consist of the following operations:
| Method | Path | Description |
|---|---|---|
GET | /rest/api/v1/agents | List licensed agents |
GET | /rest/api/v1/agents/{id} | One agent, in full |
GET | /rest/api/v1/agents/licenses/seats | Seat counts per license |
POST | /rest/api/v1/agents/create | Create one agent |
POST | /rest/api/v1/agents/create/bulk | Create up to 500 agents |
PATCH | /rest/api/v1/agents/{id} | Edit one agent |
PATCH | /rest/api/v1/agents | Edit up to 500 agents |
DELETE | /rest/api/v1/agents/{id} | Delete a XIMA agent |
GET | /rest/api/v1/agents/{id}/default-outbound-caller-id | The agent's default outbound caller ID |
PUT | /rest/api/v1/agents/{id}/default-outbound-caller-id | Set the agent's default outbound caller ID |
DELETE | /rest/api/v1/agents/{id}/default-outbound-caller-id | Clear the agent's default outbound caller ID |
GET | /rest/api/v1/agents/{id}/outbound-caller-ids | The caller IDs the phone system offers the agent |
GET | /rest/api/v1/openapi.json | The OpenAPI description (no token) |
Identifying an Agent
The {id} path parameter accepts either of the following:
| Identifier | Description |
|---|---|
| The agent's id | A UUID, for XIMA agents |
| The agent's key | name(extension), exactly as the list returns it in agent — for example, Jane Doe(101). Works for both types, and is the only way to address a UC agent. URL-encode it (Jane%20Doe(101)) |
A name or an extension on its own is never accepted: on many tenants neither is unique, and the API does not guess which agent was meant. If two phone-system users share a name(extension), the request is refused with 409 AGENT_ID_AMBIGUOUS. The list endpoint can be used to map an external system's identifiers to agent ids (see Mapping Identifiers).
Older Outbound Caller IDs EndpointThe older
…/outbound-caller-idsendpoint is the exception: it takes the agent's extension on its own, its exactname(extension)or a recording-pause alias, and not the agent's id. See Available Outbound Caller IDs.
List Agents
Lists licensed agents. The optional type query parameter filters to XIMA or UC.
Endpoint
GET /rest/api/v1/agents
GET /rest/api/v1/agents?type=XIMA
Response
{
"agents": [
{ "id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21", "agent": "Jane Doe(101)", "name": "Jane Doe",
"extension": "101", "type": "XIMA", "email": "[email protected]" },
{ "agent": "Desk Phone(2001)", "name": "Desk Phone", "extension": "2001", "type": "UC",
"email": "[email protected]" }
]
}id is omitted for UC agents.
Response Statuses
| Status | Description |
|---|---|
200 | Success |
400 | type is not XIMA or UC (older error shape) |
401 | Missing or invalid token |
Get One Agent
Returns a single agent in full.
Endpoint
GET /rest/api/v1/agents/{id}
Response
{
"id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21",
"agent": "Jane Doe(101)", "name": "Jane Doe", "extension": "101", "type": "XIMA",
"email": "[email protected]",
"profile": {
"id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21",
"name": "Jane Doe", "preferredName": "Jane", "emailAddress": "[email protected]", "extension": "101",
"connectedNumbers": [],
"noAnswerOverflow": { "destinationType": "VOICEMAIL_EXTENSION", "destination": "101" },
"recordingMode": "AUTOMATIC",
"canListenOwnRecordings": true, "canDownloadOwnRecordings": false,
"canListenSkillRecordings": false, "canDownloadSkillRecordings": false,
"canListenRoleRecordings": false, "canDownloadRoleRecordings": false
},
"licenses": ["CCAAS_VOICE", "CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON"],
"skillLevels": [ { "skillId": "ee3b0d75-…", "skillName": "Support", "skillLevel": 7 } ],
"skillSettings": { "maxSimultaneousSessions": 3, "voiceEnabled": true, "chatEnabled": true, "emailEnabled": false },
"defaultOutboundCallerId": { "label": "Main", "outboundCallerId": "8015550100" }
}profile is present for XIMA agents only. skillSettings are reported the way the admin skill grid shows them. The response can be edited and sent straight back as a PATCH body.
Response Statuses
| Status | Description |
|---|---|
200 | Success |
401 | Missing or invalid token |
404 | No agent with that id or key |
409 | AGENT_ID_AMBIGUOUS |
License Seats
Returns the seat counts the Agent Licensing page shows, per license.
Endpoint
GET /rest/api/v1/agents/licenses/seats
Response
{ "seats": [ { "license": "CCAAS_VOICE", "limit": 50, "assigned": 48, "remaining": 2, "concurrent": false } ] }remaining is negative when the tenant is over a limit.
Create an Agent
Creates one agent. Adding ?dryRun=true validates the agent without saving it (see Dry Runs).
Endpoint
POST /rest/api/v1/agents/create
POST /rest/api/v1/agents/create?dryRun=true
Request Body
{
"profile": { "name": "Jane Doe", "extension": "101", "emailAddress": "[email protected]",
"recordingMode": "AUTOMATIC" },
"licenses": ["CCAAS_VOICE"],
"skillLevels": [ { "skillName": "Support", "skillLevel": 7 } ],
"skillSettings": { "maxSimultaneousSessions": 2 }
}profile with name, extension and emailAddress is required. Anything else omitted takes a new agent's default. Omitting licenses gives the starter licenses a new agent gets in the web interface (facets.licenses.status: "DEFAULTED").
A Create Carries No Default Outbound Caller IDSending
defaultOutboundCallerIdon a create is refused with400 FIELD_NOT_ALLOWED_ON_CREATE. The phone system does not know an agent until it is saved, so it cannot say which caller IDs the agent may use. Create the agent, then set its default withPUT …/default-outbound-caller-id.
Response
Returns 201 when the agent was created (200 with VALID for a dry run), with the results body shown under How Responses Work containing one result.
Response Statuses
| Status | Description |
|---|---|
201 | Created |
200 | Dry run: would be created |
400 | The agent is invalid. errors[].reason says why |
401 | Missing or invalid token |
403 | No full access, or a corporate token (EPHEMERAL_SERVICE_USER_READ_ONLY) |
409 | Name, extension or email in use; seat limit reached; agent not licensed for a skill |
413 | The body is over 10 MiB |
429 | Another agent write is in progress. Retry |
503 | Another settings save held the lock. Nothing was saved. Retry |
Create Many Agents
Creates up to 500 agents in one settings save, each exactly as the single create.
Endpoint
POST /rest/api/v1/agents/create/bulk
Request Body
{ "dryRun": false, "agents": [ { "profile": { "…": "…" } }, { "profile": { "…": "…" } } ] }Items are applied in order, so seats and extensions taken by earlier items count against later ones. Once the request is processed the response is 200 with a result for each item, and a refused item does not stop the others, so each result must be checked. The other statuses below refuse the whole request.
The body takes only dryRun and agents. A bulk dry run is "dryRun": true in the body: the ?dryRun=true query parameter of the single-agent endpoints is refused here with 400 MALFORMED_BODY, as is any other key in the body (a misspelled dryrun, for example), so a typo cannot turn a dry run into a real write.
Response Statuses
| Status | Description |
|---|---|
200 | Processed. Check summary and each result |
400 | The body is not dryRun and a list of agents, or the URL has ?dryRun |
403 | No full access, or a corporate token. Nothing was processed |
413 | More than 500 agents, or a body over 10 MiB. Split the batch |
429 | Another agent write is in progress. Retry |
503 | Another settings save held the lock. Nothing was saved. Retry |
Update an Agent
Edits one agent. Only the fields present change (see Partial Updates (Merge Semantics)).
Endpoint
PATCH /rest/api/v1/agents/{id}
PATCH /rest/api/v1/agents/{id}?dryRun=true
Request Body
{ "profile": { "recordingMode": "MANUAL" }, "skillLevels": [ { "skillName": "Billing", "skillLevel": 0 } ] }An id in the body is optional; if sent, it must match the path (400 ID_MISMATCH).
An edit may carry defaultOutboundCallerId — a caller ID string, the {label, outboundCallerId} object GET returns, or null to clear it. A caller ID is checked with the phone system before anything is saved:
- If the agent may not use it, the request returns
409 CALLER_ID_NOT_ALLOWED, or409 CALLER_ID_NO_LABELwhen the phone system offers it without a label; nothing in the edit is written - If the phone system cannot be asked, the request returns
502 CALLER_ID_CHECK_FAILED(see caller-id-check-failed); nothing in the request is written
Response
Returns 200 with the results body containing one result, UPDATED (or VALID for a dry run). Check failures.
Response Statuses
| Status | Description |
|---|---|
200 | Updated |
400 | The edit is invalid. errors[].reason says why |
401 | Missing or invalid token |
403 | No full access, or a corporate token (EPHEMERAL_SERVICE_USER_READ_ONLY) |
404 | No agent with that id or key |
409 | In use; seat limit; not licensed for a skill; caller ID not allowed or unlabelled; ambiguous id |
413 | The body is over 10 MiB |
429 | Another agent write is in progress. Retry |
502 | The caller ID could not be checked. Nothing was saved. Retry |
503 | Another settings save held the lock. Nothing was saved. Retry |
Update Many Agents
Applies up to 500 edits in one settings save, each naming its agent in id.
Endpoint
PATCH /rest/api/v1/agents
Request Body
{ "dryRun": false, "agents": [ { "id": "53436b64-…", "licenses": ["CCAAS_VOICE"] },
{ "id": "Desk Phone(2001)", "skillLevels": [ { "skillName": "Sales", "skillLevel": 5 } ] } ] }Once the request is processed the response is 200 with a result for each item, and each result must be checked. The body is read as for Create Many Agents: only dryRun and agents, and no ?dryRun.
One Exception to Per-Item ResultsIf any item changes a
defaultOutboundCallerIdthat cannot be checked with the phone system, or the request's caller ID checks take more than 30 seconds in all, the whole request fails with502 CALLER_ID_CHECK_FAILEDand nothing in it is saved, for any agent. It is safe to send the same request again.
Response Statuses
| Status | Description |
|---|---|
200 | Processed. Check summary and each result |
400 | The body is not dryRun and a list of agents, or the URL has ?dryRun |
403 | No full access, or a corporate token. Nothing was processed |
413 | More than 500 agents, or a body over 10 MiB. Split the batch |
429 | Another agent write is in progress. Retry |
502 | A caller ID could not be checked. Nothing was saved. Retry |
503 | Another settings save held the lock. Nothing was saved. Retry |
Delete an Agent
Deletes a XIMA agent. The agent and its license seats are removed, exactly as deleting the agent in the web interface does.
Endpoint
DELETE /rest/api/v1/agents/{id}
Request Body
The agent's key is required in the body as confirmation:
{ "agent": "Jane Doe(101)" }Response
{
"dryRun": false,
"results": [
{ "index": 0, "agent": "Jane Doe(101)", "name": "Jane Doe", "extension": "101",
"email": "[email protected]", "type": "XIMA", "status": "DELETED", "warnings": [], "errors": [], "failures": [] }
],
"summary": { "created": 0, "updated": 0, "deleted": 1, "rejected": 0, "valid": 0, "withFailures": 0 }
}
Deletion Is Not Refused for an Agent in UseAs in the web interface, an agent on a call, or the last agent on a skill, is deleted without objection. Check the agent's activity and skill coverage before deleting.
The agent's skill levels and other settings stored against its name(extension) are left in place. An agent created later with the same name and extension picks them up again — useful when an agent is deleted and recreated on purpose, and worth knowing when a name and extension are reused for someone else.
Response Statuses
| Status | Description |
|---|---|
200 | Deleted |
400 | DELETE_CONFIRMATION_MISMATCH (no confirmation, or it does not match the agent) or UC_AGENT_NOT_DELETABLE |
401 | Missing or invalid token |
403 | No full access, or a corporate token (EPHEMERAL_SERVICE_USER_READ_ONLY) |
404 | No agent with that id or key |
409 | AGENT_ID_AMBIGUOUS |
413 | The body is over 10 MiB |
429 | Another agent write is in progress. Retry |
503 | Another settings save held the lock. Nothing was deleted. Retry |
A refused delete (400, 404 or 409) is a problem document that, like a refused single edit, carries the results body: one REJECTED item naming the agent, with the reason in errors. A delete has no parts, so there are no facets. The agent still exists, so a XIMA agent keeps its id:
{
"type": "https://guide.xima.cloud/docs/agent-management-apis#invalid-request",
"title": "Bad Request",
"status": 400,
"detail": "The confirmation \"Jane Doe(102)\" does not match. Confirm it by sending the agent's name(extension) in the body: {\"agent\":\"Jane Doe(101)\"}. Nothing was deleted.",
"errors": [ { "facet": "agent", "field": "agent", "reason": "DELETE_CONFIRMATION_MISMATCH", "message": "…" } ],
"dryRun": false,
"results": [
{ "index": 0, "id": "53436b64-…", "agent": "Jane Doe(101)", "name": "Jane Doe", "extension": "101",
"email": "[email protected]", "type": "XIMA", "status": "REJECTED", "warnings": [],
"errors": [ { "facet": "agent", "field": "agent", "reason": "DELETE_CONFIRMATION_MISMATCH", "message": "…" } ],
"failures": [] }
],
"summary": { "created": 0, "updated": 0, "deleted": 0, "rejected": 1, "valid": 0, "withFailures": 0 }
}Default Outbound Caller ID
Reads, sets, or clears the caller ID the agent dials out with by default (click-to-dial and outbound calls).
Endpoint
GET /rest/api/v1/agents/{id}/default-outbound-caller-id
PUT /rest/api/v1/agents/{id}/default-outbound-caller-id[?dryRun=true]
DELETE /rest/api/v1/agents/{id}/default-outbound-caller-id[?dryRun=true]
GET /rest/api/v1/agents/{id}/outbound-caller-ids
GET returns { "label": "Main", "outboundCallerId": "8015550100" }, or 204 when the agent has no default. GET …/outbound-caller-ids lists the caller IDs the phone system offers the agent; it takes the extension, name(extension) or a recording-pause alias, not the id.
Request Body
PUT sets the default:
{ "outboundCallerId": "8015550100" }PUT is an edit carrying only the caller ID, and behaves exactly like a PATCH with defaultOutboundCallerId: it is checked with the phone system first (409 CALLER_ID_NOT_ALLOWED or CALLER_ID_NO_LABEL when the agent may not use it, 502 CALLER_ID_CHECK_FAILED with nothing saved when the check cannot run). The label stored is the phone system's; a label in the body is ignored, as is any other key. A missing, empty or blank outboundCallerId is 400 CALLER_ID_REQUIRED — use DELETE to clear. The caller ID the agent already has is not a change: it is not checked and nothing is written.
Response
PUT and DELETE return the results body (200, status UPDATED, or VALID for a dry run), with the stored value in the result's defaultOutboundCallerId. Like every write, they require a Service User with full access created on the tenant: 403 otherwise, EPHEMERAL_SERVICE_USER_READ_ONLY for a corporate token.
Setting a New Agent's DefaultA new agent gets its default outbound caller ID in a second request: create the agent, then
PUTits caller ID using theidthe create returned. See New Agents and Caller IDs.
Agent Fields Reference
The following tables describe every field an agent write accepts.
Top-Level Fields
| Field | Create | Update | Description |
|---|---|---|---|
id | — | Bulk only (required) | The agent's id, or key for a UC agent |
profile | Required | Optional | XIMA agents only |
licenses | Optional | Optional | See Licenses |
skillLevels | Optional | Optional | See Skill Levels |
skillSettings | Optional | Optional | Agent-wide; see Skill Levels |
defaultOutboundCallerId | Refused (400) | Optional | null clears it. Checked with the phone system before anything is saved |
sendWelcomeEmail | Optional | — | Defaults to true |
agent, name, extension, type and email are accepted on an update so a fetched agent can be sent straight back. They are read-only; if one differs from the stored value, the result carries an IGNORED warning and nothing changes. To change the email, use profile.emailAddress.
Profile Fields
| Field | Type | Description |
|---|---|---|
name | string | Required on create. Unique across agents. Cannot contain ( ) [ or ] (400 NAME_NOT_ALLOWED). Cannot be changed |
extension | string | Required on create. Must not be used by another agent, a phone-system user or a skill. Cannot be changed |
emailAddress | string | Required on create. A valid address, unique across agents. Can be changed |
preferredName | string | Display name |
recordingMode | enum | AUTOMATIC · AUTOMATIC_PAUSING_PROHIBITED · MANUAL · DISABLED. (NOT_WEBRTC_DISABLED is refused for XIMA agents with 400 RECORDING_MODE_NOT_ALLOWED.) Cannot be cleared |
canListenOwnRecordings | boolean | Whether the agent can listen to their own recordings |
canDownloadOwnRecordings | boolean | Needs canListenOwnRecordings |
canListenSkillRecordings | boolean | Whether the agent can listen to recordings for their skills |
canDownloadSkillRecordings | boolean | Needs canListenSkillRecordings |
canListenRoleRecordings | boolean | Can only be changed while Agent Access to Recordings by Role is on (400 ROLE_RECORDINGS_NOT_ENABLED) |
canDownloadRoleRecordings | boolean | Needs canListenRoleRecordings; same setting applies |
connectedNumbers | array | Phones the agent can take calls on: name, number, disablePress1ToAnswer, disableCallLink, enableSipSubscribe |
noAnswerOverflow | object | Where an unanswered direct call goes: destinationType (IVR · SKILL · AGENT · DIALABLE_NUMBER · VOICEMAIL_EXTENSION) plus destination |
A download permission without its listen permission is refused with 400 RECORDING_PERMISSION_INVALID. The profile rules are the web interface's: anything the agent editor would not save, the API refuses.
Licenses
licenses is the agent's complete set of licenses, using the same names the Agent Licensing screen uses. Fetching an existing agent shows the names the tenant uses. Common ones:
| License | Description |
|---|---|
CCAAS_VOICE | Contact center voice agent |
CCAAS_REALTIME | Realtime-only seat. Replaces voice and its add-ons |
CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON | Chat add-on. Requires CCAAS_VOICE |
CCAAS_VOICE.CCAAS_EMAIL_ADDON | Email add-on. Requires CCAAS_VOICE |
CCAAS_CRM | CRM. Requires the agent to have an email |
CCAAS_TRANSCRIPTION | Transcription |
CCAAS_QA_EVALUATION | QA evaluation |
Tenants on UCaaS licensing use UCAAS_* names instead (for example, UCAAS_VOICE_AGENT, UCAAS_MULTI_AGENT).
- Sending
licensesreplaces that agent's licenses with the list. Other agents are not touched. - Omitting
licenseson a create gives the starter licenses (DEFAULTED). - Omitting
licenseson an update leaves them alone — except that aprofileedit withoutlicensesre-adds the starter licenses if the agent has lost them, as saving the agent in the web interface does. Sendlicensesin the same request to control them exactly. - An unknown name, a combination that cannot be held together or a missing prerequisite is refused with
400and its reason. - Seat limits apply. If granting the licenses would exceed a limit, that agent is refused with
409 SEAT_LIMIT_REACHED(detailsgiveslicense,countandlimit), and nothing else about it is written.GET …/licenses/seatsshows what is left.
Skill Levels
skillLevels and skillSettings are sent on create or update to manage which skills the agent is in and how they take work.
"skillLevels": [
{ "skillId": "ee3b0d75-9ffe-46fc-80f6-973e8c71ff8f", "skillLevel": 10 },
{ "skillName": "Sales", "skillLevel": 5 },
{ "skillName": "Billing", "skillLevel": 0 }
],
"skillSettings": { "maxSimultaneousSessions": 2, "chatEnabled": false }| Field | Where | Description |
|---|---|---|
skillId | skillLevels | The skill's id from GET /rest/api/v1/skills. The unambiguous option |
skillName | skillLevels | Alternative to skillId. Used only when skillId is absent |
skillLevel | skillLevels | 1–10 to assign, 0 to remove. Required when adding the agent to a skill |
maxSimultaneousSessions | skillSettings | 0–50 concurrent interactions |
voiceEnabled / chatEnabled / emailEnabled | skillSettings | Per-channel participation |
Skill Level Rules
Four rules govern how skill memberships and settings are written.
- Omitting
skillLevelschanges nothing. Memberships are left exactly as they were. - Supplying
skillLevelsnever removes the agent from a skill that is not listed. Listed skills are added or updated; skills the agent is on that are not listed are untouched. To remove the agent from a skill, send it withskillLevel: 0. A partial list can never silently strip an agent's skills. skillSettingsbelong to the agent, not to one skill, exactly as in the admin skill grid: a write sets them on every one of the agent's skills, and a skill the agent joins later starts from them. Sending them inside a skill entry is refused with400 PER_SKILL_SETTING_NOT_ALLOWED. Omitted settings are left as they are.- An entry that cannot be applied refuses the agent. An unknown skill (
UNKNOWN_SKILL), a skill listed twice (DUPLICATE_SKILL), a level out of range, or a new skill with noskillLevel(SKILL_LEVEL_REQUIRED_TO_ADD) is a400for a single write, or aREJECTEDitem in bulk, and nothing about that agent is written. Only voice or chat licensed agents can be on a skill (409 AGENT_NOT_LICENSED_FOR_SKILL); licenses granted in the same request count.
Write Behavior
The following rules apply across the write endpoints and are essential for a reliable integration.
Partial Updates (Merge Semantics)
On PATCH, only the fields present in the request body change. Everything omitted keeps its current value. For example, the following body changes the recording mode and leaves the name, email, permissions and everything else alone:
{ "profile": { "recordingMode": "MANUAL" } }Lists replace; they do not merge. Sending connectedNumbers: [ … ] sets the list to exactly that. licenses is the agent's complete set. skillLevels is the exception — see Skill Levels.
On POST .../create, the same rule applies against the defaults for a new agent: anything omitted is created with its default value.
Name and Extension Cannot Be Changed
A PATCH whose profile.name or profile.extension differs from the agent's current value is refused with 400 RENAME_NOT_ALLOWED. On a bulk request that item is rejected and the others still apply. Sending the current values back unchanged is fine.
This is deliberate. Much of an agent's configuration is stored against their name(extension) — role membership, preferences, the browser login allowlist, report filters and IVR transfer targets among them — and none of it follows a change. If an agent's name or extension must change, create a new agent with the new details and set it up, rather than editing the old one.
Names containing (, ), [ or ] cannot be created through the API (400 NAME_NOT_ALLOWED): the agent key name(extension) cannot carry them unambiguously.
Email can be changed freely, subject to uniqueness.
New Agents and Caller IDs
A create never sets a default outbound caller ID, so it never waits on the phone system and never fails because of it. A new agent is given its default afterwards:
ID=$(curl -s -X POST "$BASE/rest/api/v1/agents/create" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d @jane.json | jq -r '.results[0].id')
curl -s -X PUT "$BASE/rest/api/v1/agents/$ID/default-outbound-caller-id" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"outboundCallerId":"8015550100"}'A PATCH with defaultOutboundCallerId works too.
When the Phone System Cannot Be Reached
Before an edit that changes an agent's caller ID is saved, the server asks the phone system whether the agent may use it. A caller ID equal to the agent's current default, as GET returns it, is not a change: it is neither checked nor written, so a fetched agent sent back unchanged does not need the phone system.
Each check takes up to 7 seconds (2 seconds to connect, 5 to reply, 7 in all). Each agent is checked at most once per request, and a request spends at most 30 seconds on checks: a check that might not finish within that is not started. If a check cannot run — the phone system does not answer within those limits or answers with an error — or the request's checks reach 30 seconds, the request fails with 502 CALLER_ID_CHECK_FAILED, whatever else it carried:
- Nothing in the request is saved, for any agent in it, including those without a caller ID
- It is safe to send the same request again, unchanged
- A dry run checks the caller ID too, so it can also return
502
Writes without a caller ID change (creates, and edits that do not carry one or carry the current one) never ask the phone system and keep working while it is unavailable. To keep a bulk edit independent of the phone system, send caller IDs in their own requests.
After-Save Failures
Two steps run after an agent is saved and cannot undo the save:
| Reason | Facet | Description | Resolution |
|---|---|---|---|
CALLER_ID_SAVE_FAILED | defaultOutboundCallerId | The agent was saved, but its checked caller ID could not be written | PUT the caller ID again |
WELCOME_EMAIL_FAILED | welcomeEmail | The agent was created, but the welcome email could not be sent | Resend it from the agent list |
The item stays CREATED or UPDATED; the facet is FAILED; the reason is in the item's failures[]; and summary.withFailures counts it.
Do Not Re-Send a CreateAfter an after-save failure, the agent exists, and a second create is refused as a duplicate.
Dry Runs
?dryRun=true (single) or "dryRun": true in the body (bulk) checks every item exactly as a real write would, and reports the outcome with status: "VALID" or "REJECTED". That includes the phone-system check of an edit that changes a caller ID; a create carries no caller ID, so its dry run never asks. Nothing is saved, no welcome email is sent, the login portal is not updated, and a dry-run create reports no id. The bulk endpoints refuse ?dryRun (see Create Many Agents).
Welcome Emails
A created agent is sent the same welcome email the web interface sends, with a link to set their password, unless the item has "sendWelcomeEmail": false. If the email cannot be sent, the agent is still created; see After-Save Failures.
Login Portal
Every saved create or edit that changes a XIMA agent's settings updates the login portal with the agent's login, as saving the agent in the web interface does. This happens in the background, a few seconds after the response. A caller-ID-only edit (PUT/DELETE …/default-outbound-caller-id, or a PATCH carrying only defaultOutboundCallerId) changes no agent settings and does not update it.
One Write at a Time
Agent writes are serialized: one agent write at a time. A write arriving while another is in progress receives 429 WRITE_IN_PROGRESS rather than being queued. Retry on 429 — a short backoff is enough.
Do Not Send Writes in ParallelParallel write requests only produce
429responses and no extra throughput.
Agent writes also share the tenant's settings save with the web interface and the skill API. If another save holds it for too long, the write receives 503 SETTINGS_BUSY and nothing is saved. Retry.
Reads are not affected.
Errors
Every refusal carries errors[] entries of { facet, field, reason, message, details? }. facet is the part of the agent (profile, licenses, skillLevels, defaultOutboundCallerId, welcomeEmail), or agent / request for the agent or request as a whole. field is the dotted path of the field in the request body, or null when the refusal is about no single field. The problem document's type links to one of the sections below.
invalid-request
400. The request or the agent is invalid on its face. Fix it before retrying.
| Reason | Description |
|---|---|
MALFORMED_BODY | The body, a field in it or a query parameter is not what this endpoint expects; on the bulk endpoints, also a body key other than dryRun and agents, or ?dryRun |
FIELD_REQUIRED | profile (with name, extension, emailAddress) on a create, or id in a bulk edit; also a profile.recordingMode cleared, or a profile the server refused as incomplete |
FIELD_NOT_ALLOWED_ON_CREATE | defaultOutboundCallerId on a create. Create, then PUT it |
ID_MISMATCH | The body's id is not the agent in the path |
UC_PROFILE_NOT_EDITABLE | A UC agent's profile belongs to the phone system |
UC_AGENT_NOT_DELETABLE | A UC agent cannot be deleted |
DELETE_CONFIRMATION_MISMATCH | The delete body did not confirm the agent's name(extension) |
RENAME_NOT_ALLOWED | An edit changed the name or extension |
NAME_NOT_ALLOWED | The name contains ( ) [ or ] |
EMAIL_INVALID | The email address is not valid |
RECORDING_MODE_NOT_ALLOWED | A recording mode not offered for XIMA agents |
RECORDING_PERMISSION_INVALID | A download permission without its listen permission |
ROLE_RECORDINGS_NOT_ENABLED | A role recording permission changed while Agent Access to Recordings by Role is off |
UNKNOWN_LICENSE | Not a license this tenant uses |
CONFLICTING_LICENSES | The licenses cannot be held together |
ADDON_REQUIRES_VOICE | An add-on without its voice license |
LICENSE_REQUIRES_VOICE | The license needs the voice license |
LICENSE_PREREQUISITE_MISSING | The license needs another license |
CRM_REQUIRES_EMAIL | CRM needs an email address for the agent |
SKILL_REQUIRED | A skill entry has neither skillId nor skillName |
UNKNOWN_SKILL | No such skill |
DUPLICATE_SKILL | The same skill listed twice |
SKILL_LEVEL_REQUIRED_TO_ADD | A new skill needs a skillLevel above 0 |
SKILL_LEVEL_OUT_OF_RANGE | skillLevel must be 0–10 |
SESSIONS_OUT_OF_RANGE | maxSimultaneousSessions must be 0–50 |
PER_SKILL_SETTING_NOT_ALLOWED | A skill setting inside a skill entry. Send it in skillSettings |
CALLER_ID_REQUIRED | PUT …/default-outbound-caller-id without outboundCallerId, or an edit with "defaultOutboundCallerId": "" |
service-user-read-only
403 EPHEMERAL_SERVICE_USER_READ_ONLY. The token is a corporate central repository token. On a child tenant such a token is given full access whatever its Service User may do on the corporate tenant, so the agent API keeps it read-only until that is fixed for every API: reads work, and every write, dry runs included, is refused with nothing written. Use a Service User created on this tenant.
agent-not-found
404 AGENT_NOT_FOUND. No agent with that id or name(extension). It is also reported on the licenses facet when the license table cannot find the agent — for example, a UC agent removed from the phone system while the request ran.
conflict
409. The request conflicts with the tenant's configuration. Nothing was written for the agent.
| Reason | Description |
|---|---|
AGENT_ID_AMBIGUOUS | More than one phone-system user has that name(extension) |
NAME_IN_USE | Another agent has that name |
EXTENSION_IN_USE | The extension belongs to another agent, a phone-system user or a skill |
EMAIL_IN_USE | Another agent has that email address |
SEAT_LIMIT_REACHED | A license is out of seats. details: license, count, limit |
AGENT_NOT_LICENSED_FOR_SKILL | Only voice or chat licensed agents can be on a skill |
CALLER_ID_NOT_ALLOWED | The phone system does not offer this caller ID to the agent. Also returned, with "try again", if the agent was added or changed by another save while its caller ID was being checked: send the request again |
CALLER_ID_NO_LABEL | The phone system offers the caller ID without a label, so it cannot be the agent's default |
too-many-items
413 TOO_MANY_ITEMS. More than 500 agents in one request. Split the batch.
body-too-large
413 BODY_TOO_LARGE. The request's Content-Length header says its body is over 10 MiB. Nothing was read or written. Every agent management endpoint checks this first, reads included and before the token, so a 413 can come back where a 401 was expected. A chunked body without a Content-Length is not counted. 500 agents fit well within it: split the batch.
write-in-progress
429 WRITE_IN_PROGRESS. Another agent write is in progress. Retry after a short wait.
caller-id-check-failed
502 CALLER_ID_CHECK_FAILED. The phone system could not be asked whether the agent may use the caller ID (2 s to connect, 5 s to reply, up to 7 s per check, or an error), or the request's checks reached their 30-second limit. The whole request failed and nothing in it was saved. Safe to retry unchanged. See When the Phone System Cannot Be Reached.
settings-busy
503 SETTINGS_BUSY. Another settings save held the lock. Nothing was saved. Retry.
After-Save Reasons
CALLER_ID_SAVE_FAILED and WELCOME_EMAIL_FAILED are never refusals: they appear in failures[] on an item that was saved. See After-Save Failures.
Building an Integration
The following patterns cover the most common provisioning workflows.
Mapping Identifiers
Agents are addressed by id (or key for UC agents), but an external system typically keys on email or employee number. Fetching the list once and caching the mapping avoids repeated lookups:
curl -s "$BASE/rest/api/v1/agents?type=XIMA" -H "Authorization: Bearer $TOKEN" \
| jq 'reduce .agents[] as $a ({}; .[$a.email] = $a.id)'Refresh the mapping after any create.
Provisioning a New Agent
Profile, licenses and skills go in one request, and succeed or fail together. The default caller ID follows in a second request:
curl -s -X POST "$BASE/rest/api/v1/agents/create" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"profile":{"name":"Jane Doe","extension":"101","emailAddress":"[email protected]"},
"licenses":["CCAAS_VOICE"],
"skillLevels":[{"skillName":"Support","skillLevel":7}]}'
# then, with the id from results[0].id:
curl -s -X PUT "$BASE/rest/api/v1/agents/$ID/default-outbound-caller-id" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"outboundCallerId":"8015550100"}'Bulk Loading in Batches of 500
for each batch of up to 500 agents:
POST /rest/api/v1/agents/create/bulk with {"agents": [...]}
on 429 or 503 -> back off briefly and retry the same batch
for each result:
if status == REJECTED -> log result.errors, flag for a human
if failures non-empty -> agent exists; act on each failure (see After-save failures)
if warnings non-empty -> log them
then, for each created agent that needs a default caller ID:
PUT /rest/api/v1/agents/{id}/default-outbound-caller-id
on 502 -> phone system unavailable; nothing saved; retry later
Batches must not be sent in parallel — send them one at a time, sequentially.
Handling Every Response
if HTTP >= 400:
nothing was written; branch on errors[].reason
413 -> split the batch
429, 503 -> back off and retry
502 -> the phone system could not check a caller ID; retry the same request later
else:
for each result: # bulk can contain rejections in a 200
REJECTED -> nothing written; see .errors and the REJECTED facet
otherwise -> written. Then check:
.failures -> a step after the save failed (CALLER_ID_SAVE_FAILED, WELCOME_EMAIL_FAILED)
.warnings -> IGNORED: a field or read-only value was not applied — probably a typo
Do Not Ignore IGNORED WarningsTreating
IGNOREDwarnings as harmless is the most likely way to have a setting silently not apply.
Limits and Known Gaps
The following limits apply to every agent management request.
| Limit | Description |
|---|---|
| Bulk size | 500 agents per request. More returns 413 |
| Body size | 10 MiB per request on every agent management endpoint, enforced via Content-Length. More returns 413 BODY_TOO_LARGE |
| Concurrency | One agent write at a time. Others get 429 |
| Corporate tokens | Read only. Writes get 403 EPHEMERAL_SERVICE_USER_READ_ONLY |
| Name and extension | Cannot be changed. Create a new agent instead |
| Names | Cannot contain ( ) [ ] |
| Caller ID | Not on create. Set it with PUT once the agent exists. Needs the phone system |
| Skill removal | Requires an explicit skillLevel: 0 |
| UC agents | Cannot be created or deleted; profile cannot be edited |
| Delete | Not refused for an agent on a call or the last agent on a skill |
Web Interface Edits and API Writes Take Turns, but the Last One WinsThey share the settings save, so two saves never interleave, and a write that waits too long for another save gets
503; retry it. However, an administrator who saves an agent or skill page that was loaded before an API write saves what that page showed, and can undo the API's change. Avoid bulk loads while administrators are working in the agent or skill panels.
Updated 2 days ago
