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

  1. FQDN (Fully Qualified Domain Name) refers to the CCaaS organizational hostname, domain, and top-level domain. Typically formatted as: [hostname].xima.cloud.
  2. 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 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.

⚠️

Corporate Central Repository Tokens Are Read-Only

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

TypeDescriptionSupported Operations
XIMAA browser (WebRTC) agent created in XimaCreate, read, edit and delete everything
UCAn agent that comes from the 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: 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 Practice

Integrations should branch on errors[].reason, never on message or detail. 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 the 403 for a Service User without full access, which come from the shared authentication layer. The 403 for a corporate central repository token is a problem document like any other refusal (see service-user-read-only).
  • 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. 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 ?type on the list, for example, is a 400 in 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 Written

On the bulk endpoints, integrations must always check summary.rejected, summary.withFailures and each result's status.

Facets

An agent is several pieces of configuration, and each write reports on each piece separately under facets:

FacetDescription
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

Facet Statuses

Each facet reports a status:

StatusDescription
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_REQUESTEDThe 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:

StatusDescription
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 (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 (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
✅

Log Every Warning

Warnings are how typos are caught. A misspelled field name is not an error — it is simply not applied — so IGNORED warnings 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:

MethodPathDescription
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 the agent's default outbound caller ID
DELETE/rest/api/v1/agents/{id}/default-outbound-caller-idClear the agent's default outbound caller ID
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

The {id} path parameter accepts either of the following:

IdentifierDescription
The agent's idA UUID, for XIMA agents
The agent's keyname(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 Endpoint

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

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

StatusDescription
200Success
401Missing or invalid token
404No agent with that id or key
409AGENT_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 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

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

StatusDescription
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

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

StatusDescription
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

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, or 409 CALLER_ID_NO_LABEL when 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

StatusDescription
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

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

Response Statuses

StatusDescription
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

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 Use

As 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

StatusDescription
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

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 Default

A new agent gets its default outbound caller ID in a second request: create the agent, then PUT its caller ID using the id the create returned. See New Agents and Caller IDs.


Agent Fields Reference

The following tables describe every field an agent write accepts.

Top-Level Fields

FieldCreateUpdateDescription
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, the result carries an IGNORED warning and nothing changes. To change the email, use profile.emailAddress.

Profile Fields

FieldTypeDescription
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
canListenOwnRecordingsbooleanWhether the agent can listen to their own recordings
canDownloadOwnRecordingsbooleanNeeds canListenOwnRecordings
canListenSkillRecordingsbooleanWhether the agent can listen to recordings for their skills
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. Fetching an existing agent shows the names the tenant uses. Common ones:

LicenseDescription
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

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 }
FieldWhereDescription
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

Skill Level Rules

Four rules govern how skill memberships and settings are written.

  1. Omitting skillLevels changes nothing. Memberships are left exactly as they were.
  2. Supplying skillLevels never 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 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.

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:

ReasonFacetDescriptionResolution
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

After 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 Parallel

Parallel write requests only produce 429 responses 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.

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

ReasonDescription
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 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"}'
✅

Best Practice

Run the create with ?dryRun=true first.

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 Warnings

Treating IGNORED warnings 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.

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

📘

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.