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

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 receives 403. 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

TypeWhat it isWhat you can do
XIMAA browser (WebRTC) agent created in XimaCreate, read, edit and delete everything
UCAn agent that comes from your phone systemRead; 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"

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:

FacetCovers
profileName, extension, email, recording mode and permissions, connected numbers, no-answer overflow
licensesThe agent's licenses
skillLevelsThe agent's skill memberships and agent-wide skill settings
defaultOutboundCallerIdThe caller ID the agent dials out with by default
welcomeEmailThe welcome email sent to a new agent

Each facet has a status:

statusMeaning
APPLIEDWritten
DEFAULTEDlicenses on a create that named none: the starter licenses a new agent gets in the web interface
VALIDDry run only: would have been written
REJECTEDRefused, so nothing in the agent was written. errors says why
NOT_APPLIEDAnother part of the agent was refused, so this part was not written either
FAILEDRan after the agent was saved and failed. The rest of the agent stands. See After-save failures
NOT_REQUESTEDYour 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

statusMeaning
CREATEDA new agent was created
UPDATEDAn existing agent was changed
DELETEDThe agent was deleted
REJECTEDNothing was written for this agent. See errors
VALIDDry 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 (inside connectedNumbers[] and noAnswerOverflow too), in a skill entry or in skillSettings;
  • a read-only field (agent, name, extension, type, email) sent on an edit with a value that differs from the stored one;
  • profile.id on a create (the server assigns it), and sendWelcomeEmail on 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

MethodPathPurpose
GET/rest/api/v1/agentsList licensed agents
GET/rest/api/v1/agents/{id}One agent, in full
GET/rest/api/v1/agents/licenses/seatsSeat counts per license
POST/rest/api/v1/agents/createCreate one agent
POST/rest/api/v1/agents/create/bulkCreate up to 500 agents
PATCH/rest/api/v1/agents/{id}Edit one agent
PATCH/rest/api/v1/agentsEdit up to 500 agents
DELETE/rest/api/v1/agents/{id}Delete a XIMA agent
GET/rest/api/v1/agents/{id}/default-outbound-caller-idThe agent's default outbound caller ID
PUT/rest/api/v1/agents/{id}/default-outbound-caller-idSet it
DELETE/rest/api/v1/agents/{id}/default-outbound-caller-idClear it
GET/rest/api/v1/agents/{id}/outbound-caller-idsThe caller IDs the phone system offers the agent
GET/rest/api/v1/openapi.jsonThe OpenAPI description (no token)

Identifying an agent

{id} can be either of:

  • 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 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.

StatusMeaning
200Success
400type is not XIMA or UC (older error shape)
401Missing 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.

StatusMeaning
200Success
401Missing or invalid token
404No agent with that id or key
409AGENT_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 defaultOutboundCallerId on a create
is refused with 400 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 with PUT …/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.

StatusMeaning
201Created
200Dry run: would be created
400The agent is invalid. errors[].reason says why
401Missing or invalid token
403No full access, or a corporate token (EPHEMERAL_SERVICE_USER_READ_ONLY)
409Name, extension or email in use; seat limit reached; agent not licensed for a skill
413The body is over 10 MiB
429Another agent write is in progress. Retry
503Another 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.

StatusMeaning
200Processed. Check summary and each result
400The body is not dryRun and a list of agents, or the URL has ?dryRun
403No full access, or a corporate token. Nothing was processed
413More than 500 agents, or a body over 10 MiB. Split the batch
429Another agent write is in progress. Retry
503Another 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, or 409 CALLER_ID_NO_LABEL when 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.

StatusMeaning
200Updated
400The edit is invalid. errors[].reason says why
401Missing or invalid token
403No full access, or a corporate token (EPHEMERAL_SERVICE_USER_READ_ONLY)
404No agent with that id or key
409In use; seat limit; not licensed for a skill; caller ID not allowed or unlabelled; ambiguous id
413The body is over 10 MiB
429Another agent write is in progress. Retry
502The caller ID could not be checked. Nothing was saved. Retry
503Another 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.

StatusMeaning
200Processed. Check summary and each result
400The body is not dryRun and a list of agents, or the URL has ?dryRun
403No full access, or a corporate token. Nothing was processed
413More than 500 agents, or a body over 10 MiB. Split the batch
429Another agent write is in progress. Retry
502A caller ID could not be checked. Nothing was saved. Retry
503Another 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.

StatusMeaning
200Deleted
400DELETE_CONFIRMATION_MISMATCH (no confirmation, or it does not match the agent) or UC_AGENT_NOT_DELETABLE
401Missing or invalid token
403No full access, or a corporate token (EPHEMERAL_SERVICE_USER_READ_ONLY)
404No agent with that id or key
409AGENT_ID_AMBIGUOUS
413The body is over 10 MiB
429Another agent write is in progress. Retry
503Another 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

FieldCreateUpdateNotes
id—Bulk only (required)The agent's id, or key for a UC agent
profileRequiredOptionalXIMA agents only
licensesOptionalOptionalSee Licenses
skillLevelsOptionalOptionalSee Skill levels
skillSettingsOptionalOptionalAgent-wide; see Skill levels
defaultOutboundCallerIdRefused (400)Optionalnull clears it. Checked with the phone system before anything is saved
sendWelcomeEmailOptional—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

FieldTypeNotes
namestringRequired on create. Unique across agents. Cannot contain ( ) [ or ] (400 NAME_NOT_ALLOWED). Cannot be changed
extensionstringRequired on create. Must not be used by another agent, a phone-system user or a skill. Cannot be changed
emailAddressstringRequired on create. A valid address, unique across agents. Can be changed
preferredNamestringDisplay name
recordingModeenumAUTOMATIC · AUTOMATIC_PAUSING_PROHIBITED · MANUAL · DISABLED. (NOT_WEBRTC_DISABLED is refused for XIMA agents with 400 RECORDING_MODE_NOT_ALLOWED.) Cannot be cleared
canListenOwnRecordingsboolean
canDownloadOwnRecordingsbooleanNeeds canListenOwnRecordings
canListenSkillRecordingsboolean
canDownloadSkillRecordingsbooleanNeeds canListenSkillRecordings
canListenRoleRecordingsbooleanCan only be changed while Agent Access to Recordings by Role is on (400 ROLE_RECORDINGS_NOT_ENABLED)
canDownloadRoleRecordingsbooleanNeeds canListenRoleRecordings; same setting applies
connectedNumbersarrayPhones the agent can take calls on: name, number, disablePress1ToAnswer, disableCallLink, enableSipSubscribe
noAnswerOverflowobjectWhere 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:

LicenseMeaning
CCAAS_VOICEContact center voice agent
CCAAS_REALTIMERealtime-only seat. Replaces voice and its add-ons
CCAAS_VOICE.CCAAS_WEB_CHAT_ADDONChat add-on. Requires CCAAS_VOICE
CCAAS_VOICE.CCAAS_EMAIL_ADDONEmail add-on. Requires CCAAS_VOICE
CCAAS_CRMCRM. Requires the agent to have an email
CCAAS_TRANSCRIPTIONTranscription
CCAAS_QA_EVALUATIONQA evaluation

Tenants on UCaaS licensing use UCAAS_* names instead (for example UCAAS_VOICE_AGENT,
UCAAS_MULTI_AGENT).

  • Sending licenses replaces that agent's licenses with the list. Other agents are not
    touched.
  • Omitting licenses on a create gives the starter licenses (DEFAULTED).
  • Omitting licenses on an update leaves them alone — except that a profile edit without
    licenses re-adds the starter licenses
    if the agent has lost them, as saving the agent in the
    web interface does. Send licenses in the same request to control them exactly.
  • An unknown name, a combination that cannot be held together or a missing prerequisite is refused
    with 400 and its reason.
  • Seat limits apply. If granting the licenses would exceed a limit, that agent is refused with
    409 SEAT_LIMIT_REACHED (details gives license, count and limit), and nothing else about
    it is written. GET …/licenses/seats shows 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 }
FieldWhereNotes
skillIdskillLevelsThe skill's id from GET /rest/api/v1/skills. The unambiguous option
skillNameskillLevelsAlternative to skillId. Used only when skillId is absent
skillLevelskillLevels1–10 to assign, 0 to remove. Required when adding the agent to a skill
maxSimultaneousSessionsskillSettings0–50 concurrent interactions
voiceEnabled / chatEnabled / emailEnabledskillSettingsPer-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.

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 it, send caller IDs in
their own requests.

After-save failures

Two steps run after an agent is saved and cannot undo the save:

ReasonFacetWhat happenedWhat to do
CALLER_ID_SAVE_FAILEDdefaultOutboundCallerIdThe agent was saved, but its checked caller ID could not be writtenPUT the caller ID again
WELCOME_EMAIL_FAILEDwelcomeEmailThe agent was created, but the welcome email could not be sentResend 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.

ReasonMeaning
MALFORMED_BODYThe 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_REQUIREDprofile (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_CREATEdefaultOutboundCallerId on a create. Create, then PUT it
ID_MISMATCHThe body's id is not the agent in the path
UC_PROFILE_NOT_EDITABLEA UC agent's profile belongs to the phone system
UC_AGENT_NOT_DELETABLEA UC agent cannot be deleted
DELETE_CONFIRMATION_MISMATCHThe delete body did not confirm the agent's name(extension)
RENAME_NOT_ALLOWEDAn edit changed the name or extension
NAME_NOT_ALLOWEDThe name contains ( ) [ or ]
EMAIL_INVALIDThe email address is not valid
RECORDING_MODE_NOT_ALLOWEDA recording mode not offered for XIMA agents
RECORDING_PERMISSION_INVALIDA download permission without its listen permission
ROLE_RECORDINGS_NOT_ENABLEDA role recording permission changed while Agent Access to Recordings by Role is off
UNKNOWN_LICENSENot a license this tenant uses
CONFLICTING_LICENSESThe licenses cannot be held together
ADDON_REQUIRES_VOICEAn add-on without its voice license
LICENSE_REQUIRES_VOICEThe license needs the voice license
LICENSE_PREREQUISITE_MISSINGThe license needs another license
CRM_REQUIRES_EMAILCRM needs an email address for the agent
SKILL_REQUIREDA skill entry has neither skillId nor skillName
UNKNOWN_SKILLNo such skill
DUPLICATE_SKILLThe same skill listed twice
SKILL_LEVEL_REQUIRED_TO_ADDA new skill needs a skillLevel above 0
SKILL_LEVEL_OUT_OF_RANGEskillLevel must be 0–10
SESSIONS_OUT_OF_RANGEmaxSimultaneousSessions must be 0–50
PER_SKILL_SETTING_NOT_ALLOWEDA skill setting inside a skill entry. Send it in skillSettings
CALLER_ID_REQUIREDPUT …/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.

ReasonMeaning
AGENT_ID_AMBIGUOUSMore than one phone-system user has that name(extension)
NAME_IN_USEAnother agent has that name
EXTENSION_IN_USEThe extension belongs to another agent, a phone-system user or a skill
EMAIL_IN_USEAnother agent has that email address
SEAT_LIMIT_REACHEDA license is out of seats. details: license, count, limit
AGENT_NOT_LICENSED_FOR_SKILLOnly voice or chat licensed agents can be on a skill
CALLER_ID_NOT_ALLOWEDThe 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_LABELThe 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 size500 agents per request. More returns 413
Body size10 MiB per request on every agent management endpoint, enforced via Content-Length. More returns 413 BODY_TOO_LARGE
ConcurrencyOne agent write at a time. Others get 429
Corporate tokensRead only. Writes get 403 EPHEMERAL_SERVICE_USER_READ_ONLY
Name and extensionCannot be changed. Create a new agent instead
NamesCannot contain ( ) [ ]
Caller IDNot on create. Set it with PUT once the agent exists. Needs the phone system
Skill removalRequires an explicit skillLevel: 0
UC agentsCannot be created or deleted; profile cannot be edited
DeleteNot 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.