Agent Management APIs
Create, read and update agents programmatically: profile, licenses, skill levels and default outbound caller ID.
Create, read, edit and delete agents programmatically: profile, licenses, skill levels and default
outbound caller ID.
A machine-readable description of every endpoint, field and error reason on this page is served by
your tenant at https://<your-tenant>/rest/api/v1/openapi.json (OpenAPI 3.1, no token needed).
Every response from the agent management endpoints links to it and to this page in its Link
header. The agent list and …/outbound-caller-ids, which are older than this API, do not, nor do
the 405, 406 and 415 refusals described below; a 413 BODY_TOO_LARGE may not.
Contents
- Getting started
- How responses work
- Endpoints
- Agent fields reference
- Skill levels
- Behaviour you need to understand
- Errors
- Building an integration
- Limits and known gaps
Getting started
Base URL
https://<your-tenant>/rest/api/v1/agents
Replace <your-tenant> with your own hostname. Note the /rest prefix — it is easy to leave out.
Authentication
Every request needs a Service User token:
Authorization: Bearer <your-token>
Create a Service User in the web interface under API Enablement → Service Users, then copy its token.
Writes require a Service User with full access. Any 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 receives403. Every change is recorded in the settings
audit log against the Service User that made it.A 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. Use a
Service User created on the tenant you are writing to.
Content type
All request bodies are JSON. Send Content-Type: application/json.
Your first call
curl -s "https://<your-tenant>/rest/api/v1/agents" \
-H "Authorization: Bearer $TOKEN"Two kinds of agent
| Type | What it is | What you can do |
|---|---|---|
XIMA | A browser (WebRTC) agent created in Xima | Create, read, edit and delete everything |
UC | An agent that comes from your 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.
Request-level failures → HTTP error status
The whole request was refused. Nothing was written. The body is an
RFC 9457 problem document
(Content-Type: application/problem+json):
{
"type": "https://guide.xima.cloud/docs/agent-management-apis#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 }
}Branch on errors[].reason, never on message or detail: reasons are a fixed list
(Errors); messages are 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, so you can see 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.
401, and the 403 for a Service User without full access, come from the shared authentication
layer and use the older shape { "exception": "…", "message": "…" }. The 403 for a corporate
central repository token is a problem document like any other refusal
(service-user-read-only).
A query parameter of the wrong type (?dryRun=maybe) is 400 MALFORMED_BODY. A few refusals are
made before the request reaches the agent API, and use the older { "exception": "…", "message": "…" }
shape: 415 for a Content-Type other than application/json, 405 for a method the path does not
take, and 406 for an Accept header that rules out JSON. The two older endpoints, the agent list
and …/outbound-caller-ids, do not use problem documents at all: an unknown ?type on the list,
for example, is a 400 in the older shape.
Item-level failures on the bulk endpoints → HTTP 200, with status: "REJECTED"
status: "REJECTED"The request was understood and processed, but one or more agents in it could not be written.
This still returns 200. 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 }
}On the bulk endpoints, always check summary.rejected, summary.withFailures and each result's
status. A 200 there does not mean every agent was written.
Facets
An agent is several pieces of configuration, and each write reports on each piece separately
under facets:
| Facet | Covers |
|---|---|
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 |
Each facet has a status:
status | Meaning |
|---|---|
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 | Your 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
status | Meaning |
|---|---|
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 (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 you sent 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 recognised, 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.
Warnings are how you catch typos. A misspelled field name is not an error — it is not applied — so
IGNORED warnings are the only signal that you sent something the API did not understand.
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
| Method | Path | Purpose |
|---|---|---|
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 it |
DELETE | /rest/api/v1/agents/{id}/default-outbound-caller-id | Clear it |
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
{id} can be either of:
- The agent's id — a UUID, for
XIMAagents. - The agent's key —
name(extension), exactly as the list returns it inagent, for example
Jane Doe(101). Works for both types, and is the only way to address aUCagent.
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 will not guess which agent you meant. (The older …/outbound-caller-ids endpoint is the
exception: it takes the agent's extension on its own, its exact name(extension) or a
recording-pause alias, and not the agent's id.) If two phone-system users share a name(extension) the
request is refused with 409 AGENT_ID_AMBIGUOUS. Use the list endpoint to map your own identifiers.
List agents
GET /rest/api/v1/agents
GET /rest/api/v1/agents?type=XIMA
Response 200
{
"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]" }
]
}Lists licensed agents. type filters to XIMA or UC. id is omitted for UC agents.
| Status | Meaning |
|---|---|
200 | Success |
400 | type is not XIMA or UC (older error shape) |
401 | Missing or invalid token |
Get one agent
GET /rest/api/v1/agents/{id}
Response 200
{
"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.
| Status | Meaning |
|---|---|
200 | Success |
401 | Missing or invalid token |
404 | No agent with that id or key |
409 | AGENT_ID_AMBIGUOUS |
License seats
GET /rest/api/v1/agents/licenses/seats
{ "seats": [ { "license": "CCAAS_VOICE", "limit": 50, "assigned": 48, "remaining": 2, "concurrent": false } ] }The numbers the Agent Licensing page shows. remaining is negative when the tenant is over a limit.
Create an agent
POST /rest/api/v1/agents/create
POST /rest/api/v1/agents/create?dryRun=true
{
"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. Everything else you omit 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 ID. Sending
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 201 when the agent was created (200 with VALID for a dry run): the results body
shown under How responses work, with one result.
| Status | Meaning |
|---|---|
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
POST /rest/api/v1/agents/create/bulk
{ "dryRun": false, "agents": [ { "profile": { "…": "…" } }, { "profile": { "…": "…" } } ] }Up to 500 agents, created in one settings save, each exactly as the single create. Items are
applied in order, so seats and extensions taken by earlier items count against later ones.
Once the request is processed the answer is 200 with a result for each item, and a refused item
does not stop the others: check each result. 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, say), so a typo
cannot turn a dry run into a real write.
| Status | Meaning |
|---|---|
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
PATCH /rest/api/v1/agents/{id}
PATCH /rest/api/v1/agents/{id}?dryRun=true
{ "profile": { "recordingMode": "MANUAL" }, "skillLevels": [ { "skillName": "Billing", "skillLevel": 0 } ] }Only the fields present change (merge semantics). 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:
- the agent may not use it →
409 CALLER_ID_NOT_ALLOWED, or409 CALLER_ID_NO_LABELwhen the
phone system offers it without a label; nothing in the edit is written; - the phone system cannot be asked →
502 CALLER_ID_CHECK_FAILED
(below); nothing in the request is written.
Response 200: the results body with one result, UPDATED (or VALID for a dry run). Check
failures.
| Status | Meaning |
|---|---|
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
PATCH /rest/api/v1/agents
{ "dryRun": false, "agents": [ { "id": "53436b64-…", "licenses": ["CCAAS_VOICE"] },
{ "id": "Desk Phone(2001)", "skillLevels": [ { "skillName": "Sales", "skillLevel": 5 } ] } ] }Up to 500 edits in one settings save, each naming its agent in id. Once the request is
processed the answer is 200 with a result for each item: check each result. The body is read as
for Create many agents: only dryRun and agents, and no ?dryRun.
One exception to per-item results: if any item changes a defaultOutboundCallerId that 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 with 502 CALLER_ID_CHECK_FAILED and nothing in it is saved, for
any agent. It is safe to send the same request again.
| Status | Meaning |
|---|---|
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
DELETE /rest/api/v1/agents/{id}
Deletes a XIMA agent. Required: the agent's key in the body, as confirmation:
{ "agent": "Jane Doe(101)" }Response 200
{
"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 }
}The agent and its license seats are removed, exactly as deleting the agent in the web interface
does.
Deletion is not refused for an agent in use. As in the web interface, an agent on a call,
or the last agent on a skill, is deleted without objection. Check before you delete.
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 — which is useful
when you delete and recreate an agent on purpose, and worth knowing when you reuse a name and
extension for someone else.
| Status | Meaning |
|---|---|
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
The caller ID the agent dials out with by default (click-to-dial and outbound calls).
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.
PUT sets the default:
{ "outboundCallerId": "8015550100" }It is an edit carrying only the caller ID, and behaves exactly like a PATCH with
defaultOutboundCallerId: 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.
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 need a Service User with full access
created on the tenant: 403 otherwise, EPHEMERAL_SERVICE_USER_READ_ONLY for a corporate token.
This is how a new agent gets its default: create the agent, then PUT its caller ID using the
id the create returned.
Agent fields reference
Top level of a write
| Field | Create | Update | Notes |
|---|---|---|---|
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 you get an
IGNORED warning and nothing changes. To change the email, use profile.emailAddress.
Profile fields
| Field | Type | Notes |
|---|---|---|
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 | |
canDownloadOwnRecordings | boolean | Needs canListenOwnRecordings |
canListenSkillRecordings | boolean | |
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. Fetch an existing agent to see the names your tenant uses. Common ones:
| License | Meaning |
|---|---|
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 without
licensesre-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
with400and 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
Send skillLevels and skillSettings 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 | Notes |
|---|---|---|
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 |
Four rules
1. Omitting skillLevels changes nothing. Memberships are left exactly as they were.
2. Supplying skillLevels never removes anyone from a skill you did not list. Listed skills
are added or updated; skills the agent is on and you did not list are untouched. To remove the
agent from a skill, send it with skillLevel: 0. A partial list can never silently strip an
agent's skills.
3. skillSettings belong 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 with 400 PER_SKILL_SETTING_NOT_ALLOWED.
Omitted settings are left as they are.
4. 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 no skillLevel
(SKILL_LEVEL_REQUIRED_TO_ADD) is a 400 for a single write, or a REJECTED item 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.
Behaviour you need to understand
Partial updates (merge semantics)
On PATCH, only the fields present in your body change. Everything omitted keeps its current
value. This:
{ "profile": { "recordingMode": "MANUAL" } }changes the recording mode and leaves the name, email, permissions and everything else alone.
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 you
omit 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. Give a new agent 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.
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 it, send caller IDs in
their own requests.
Two steps run after an agent is saved and cannot undo the save:
| Reason | Facet | What happened | What to do |
|---|---|---|---|
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 create — 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
(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 serialised: one agent write at a time. A write arriving while
another is in progress gets 429 WRITE_IN_PROGRESS rather than being queued. Retry on
429 — a short backoff is enough. Do not run parallel write requests; you will simply get 429s
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 gets 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 | Meaning |
|---|---|
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 | Meaning |
|---|---|
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 you expected 401. 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
Map your identifiers once
Agents are addressed by id (or key for UC agents), but your own system probably keys on email or
employee number. Fetch the list once and cache the mapping:
curl -s "$BASE/rest/api/v1/agents?type=XIMA" -H "Authorization: Bearer $TOKEN" \
| jq 'reduce .agents[] as $a ({}; .[$a.email] = $a.id)'Refresh it after any create.
Provision 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"}'Try it with ?dryRun=true first.
Bulk load 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
Do not send batches in parallel. One at a time, sequentially.
Handle every response properly
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
Treating IGNORED warnings as harmless is the most likely way to have a setting silently not
apply.
Limits and known gaps
| 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 wins. They share the settings
save, so two saves never interleave, and a write that waits too long for another save gets 503;
retry it. But an administrator who saves an agent or skill page that was loaded before your write
saves what that page showed, and can undo your change. Avoid bulk loads while administrators are
working in the agent or skill panels.
The list returns licensed agents only. An agent with no license does not appear in
GET /rest/api/v1/agents, but can still be read and updated by id or key.
Updated 2 days ago
