Agent Management APIs

Create, read and update agents programmatically: profile, licenses, skill levels and default outbound caller ID.



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

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 everything
UCAn agent that comes from your phone systemRead; edit licenses, skill levels and default outbound caller ID. Cannot be created, 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 rejected. Nothing was written.

{
  "exception": "ApiException",
  "message": "profile.extension: Extension 101 is already used by another agent."
}

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 update that cannot be
written fails the request with a 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)", "status": "CREATED", "…": "…" },
    { "index": 1, "agent": "John Roe(102)", "status": "REJECTED",
      "facets": { "licensing": { "status": "FAILED", "errors": [ { "field": "licenses",
        "reason": "User addon count of 51 exceeded the maximum of 50 for CCAAS_WEB_CHAT_ADDON" } ] } },
      "errors": [ { "field": "licenses", "reason": "User addon count of 51 exceeded the maximum of 50 for CCAAS_WEB_CHAT_ADDON" } ] }
  ],
  "summary": { "created": 1, "updated": 0, "partial": 0, "rejected": 1, "valid": 0 }
}

On the bulk endpoints, always check summary.rejected, summary.partial 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
licensingThe agent's licenses
skillLevelsThe agent's skill memberships
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
FAILEDNot written. errors says why
NOT_REQUESTEDYour request did not ask to change it
NOT_ATTEMPTEDNot tried, because something it depends on failed first

profile, licensing and skillLevels are saved together: for each agent, either all three
are written or none are.
If an agent's licenses fail, its profile and skill levels 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
PARTIALThe agent was saved, but its default outbound caller ID was not. See Partial results
REJECTEDNothing was written for this agent. See errors
VALIDDry run only: this agent would have been written

id is present exactly when the agent exists after the request. A rejected create has no id.
UC agents never have one.

Warnings

A warning means the write happened, and something in it was adjusted or not applied. Every
write response carries a warnings[] array per item.

{
  "field": "skillLevels[0] (Billing)",
  "action": "IGNORED",
  "reason": "skillLevel is required to add the agent to a skill they are not already on."
}
actionMeaning
DEFAULTEDYou omitted or sent something that could not be used, so a default was applied. resolvedTo says what
IGNOREDThe field name was not recognised, a skill entry could not be applied, or a read-only field was sent with a different value. Nothing was applied for it

Warnings are how you catch typos. A misspelled field name is not an error — it is silently not
applied — so IGNORED warnings are the only signal that you sent something the API did not
understand. Log them.


Endpoints

MethodPathPurpose
GET/rest/api/v1/agentsList licensed agents
GET/rest/api/v1/agents/{id}One agent in full
POST/rest/api/v1/agents/createCreate an agent
POST/rest/api/v1/agents/create/bulkCreate many agents at once
PATCH/rest/api/v1/agents/{id}Update an agent
PATCH/rest/api/v1/agentsUpdate many agents at once
GET/rest/api/v1/agents/{id}/default-outbound-caller-idRead the 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

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. 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
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": "JD",
    "emailAddress": "[email protected]",
    "extension": "101",
    "connectedNumbers": [],
    "noAnswerOverflow": { "destinationType": "VOICEMAIL_EXTENSION" },
    "recordingMode": "AUTOMATIC",
    "canListenOwnRecordings": true,
    "canListenSkillRecordings": false,
    "canDownloadOwnRecordings": false,
    "canDownloadSkillRecordings": false,
    "canListenRoleRecordings": false,
    "canDownloadRoleRecordings": false
  },
  "licenses": ["CCAAS_VOICE", "CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON"],
  "skillLevels": [
    { "skillId": "ee3b0d75-9ffe-46fc-80f6-973e8c71ff8f", "skillName": "Support", "skillLevel": 5,
      "maxSimultaneousSessions": 3, "voiceEnabled": true, "chatEnabled": false, "emailEnabled": true }
  ],
  "defaultOutboundCallerId": { "label": "Main line", "outboundCallerId": "+18015550100" }
}

This is the same shape the write endpoints accept, so you can fetch an agent, change a value,
and send it back. Sending it back unchanged changes nothing.

  • profile is absent for UC agents.
  • skillLevels lists only skills the agent is on (skill level above 0).
  • defaultOutboundCallerId is absent when the agent has none.
StatusMeaning
200Success
401Missing or invalid token
404No agent with that id or key

Create an agent

POST /rest/api/v1/agents/create
POST /rest/api/v1/agents/create?dryRun=true

Creates a XIMA agent. Required: profile with name, extension and emailAddress.
Everything else is optional.

Request

{
  "profile": {
    "name": "Jane Doe",
    "extension": "101",
    "emailAddress": "[email protected]",
    "recordingMode": "AUTOMATIC"
  },
  "licenses": ["CCAAS_VOICE", "CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON"],
  "skillLevels": [
    { "skillName": "Support", "skillLevel": 7 }
  ],
  "defaultOutboundCallerId": "+18015550100",
  "sendWelcomeEmail": true
}
FieldNotes
profileSee Profile fields. Omitted fields take the same defaults the web interface uses for a new agent
licensesSee Licenses. If omitted, the agent gets the license the web interface gives every new agent, and a DEFAULTED warning says so
skillLevelsSee Skill levels
defaultOutboundCallerIdMust be a caller ID this agent is allowed to use
sendWelcomeEmailDefaults to true, as in the web interface. The email lets the agent set their password

Response 201

{
  "dryRun": false,
  "results": [ {
    "index": 0,
    "id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21",
    "agent": "Jane Doe(101)", "name": "Jane Doe", "extension": "101",
    "email": "[email protected]", "type": "XIMA",
    "status": "CREATED",
    "facets": {
      "profile": { "status": "APPLIED" },
      "licensing": { "status": "APPLIED", "licenses": ["CCAAS_VOICE", "CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON"] },
      "skillLevels": { "status": "APPLIED", "applied": 1 },
      "defaultOutboundCallerId": { "status": "APPLIED" },
      "welcomeEmail": { "status": "APPLIED" }
    },
    "warnings": [], "errors": []
  } ],
  "summary": { "created": 1, "updated": 0, "partial": 0, "rejected": 0, "valid": 0 }
}

The new agent's id is in results[0].id. Store it.

With ?dryRun=true nothing is saved and no email is sent; the result has status: "VALID" and the
response is 200.

StatusMeaning
201Created — including PARTIAL, where the agent exists but its default outbound caller ID was not set
200Dry run
400profile missing, a required profile field blank, or the body could not be read (for example a value that is not a valid choice for its field)
401Missing or invalid token
403The Service User does not have full access
409Name, extension or email already in use, or the licenses could not be granted (seat limit, add-on without its base license, CRM without an email)
429Another write is in progress. Retry

Create many agents

POST /rest/api/v1/agents/create/bulk

Use this for anything involving more than a couple of agents. It is not just fewer round
trips: each write triggers a full configuration save and, on Netsapiens tenants, a complete
dial-plan reconciliation. A bulk request does that once regardless of how many agents it
carries.

Request

{
  "dryRun": false,
  "agents": [
    { "profile": { "name": "Jane Doe", "extension": "101", "emailAddress": "[email protected]" } },
    { "profile": { "name": "John Roe", "extension": "102", "emailAddress": "[email protected]" },
      "licenses": ["CCAAS_VOICE"], "sendWelcomeEmail": false }
  ]
}

Each item has the same fields as a single create.

Response 200 — one result per item, in the order you sent them (index matches).

A failing item does not stop the others. One duplicate extension in item 300 of 500 does not
discard the other 499. Items are checked against each other too, so two items in one request
cannot claim the same name, extension or email.

StatusMeaning
200Processed. Check each status and the summary — individual agents may still have been rejected
400No agents supplied
401Missing or invalid token
403The Service User does not have full access
413More than 500 items. Split into smaller batches
429Another write is in progress. Retry

Update an agent

PATCH /rest/api/v1/agents/{id}
PATCH /rest/api/v1/agents/{id}?dryRun=true

Required: nothing. Send only what you are changing.

Request — change the recording mode and add one skill, nothing else:

{
  "profile": { "recordingMode": "MANUAL" },
  "skillLevels": [ { "skillName": "Sales", "skillLevel": 4 } ]
}

Every other profile field, every other skill membership, and the agent's licenses are left
exactly as they were. See Partial updates.

You may include id; if you do, it must match the agent in the path.

Response 200

{
  "dryRun": false,
  "results": [ {
    "index": 0, "id": "53436b64-…", "agent": "Jane Doe(101)", "status": "UPDATED",
    "facets": {
      "profile": { "status": "APPLIED" },
      "licensing": { "status": "NOT_REQUESTED" },
      "skillLevels": { "status": "APPLIED", "applied": 1 },
      "defaultOutboundCallerId": { "status": "NOT_REQUESTED" },
      "welcomeEmail": { "status": "NOT_REQUESTED" }
    },
    "warnings": [], "errors": []
  } ],
  "summary": { "created": 0, "updated": 1, "partial": 0, "rejected": 0, "valid": 0 }
}
StatusMeaning
200Updated (or dry run)
400A name or extension change was attempted, a UC agent's profile was sent, the id in the body does not match the path, or the body could not be read
401Missing or invalid token
403The Service User does not have full access
404No agent with that id or key
409The email is already in use, or the licenses could not be granted
429Another write is in progress. Retry

Update many agents

PATCH /rest/api/v1/agents

Request

{
  "dryRun": false,
  "agents": [
    { "id": "53436b64-…", "profile": { "recordingMode": "MANUAL" } },
    { "id": "Desk Phone(2001)", "licenses": ["CCAAS_VOICE"] }
  ]
}

Each item must carry id — the agent's id, or its key for a UC agent. Otherwise each item
is the same as a single update.

StatusMeaning
200Processed. Check each status and the summary
400No agents supplied
401Missing or invalid token
403The Service User does not have full access
413More than 500 items. Split into smaller batches
429Another write is in progress. Retry

Default outbound caller ID

The caller ID an agent dials out with when they do not pick one. Click-to-dial uses it when the
request does not name a caller ID. The agent sees and can change the same setting in their own
menu.

GET    /rest/api/v1/agents/{id}/default-outbound-caller-id
PUT    /rest/api/v1/agents/{id}/default-outbound-caller-id
DELETE /rest/api/v1/agents/{id}/default-outbound-caller-id

GET response 200, or 204 when the agent has no default:

{ "label": "Main line", "outboundCallerId": "+18015550100" }

PUT request

{ "outboundCallerId": "+18015550100" }

It must be one of the caller IDs the agent is allowed to use — the list from
GET /rest/api/v1/agents/{id}/outbound-caller-ids. The response returns the saved value with its
label.

DELETE clears it and returns 204.

StatusMeaning
200 / 204Success
400The caller ID is not one this agent may use, or is blank
401Missing or invalid token
403The Service User does not have full access (PUT, DELETE)
404No agent with that id or key

The same setting can be written as defaultOutboundCallerId in a create or update. There it
accepts either the caller ID as a string or the {label, outboundCallerId} object that
GET /agents/{id} returns, and null clears it.


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
licensesOptionalOptional
skillLevelsOptionalOptional
defaultOutboundCallerIdOptionalOptionalnull clears it on an update
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 be changed
extensionstringRequired on create. Unique across agents. Cannot be changed
emailAddressstringRequired on create. Unique across agents. Can be changed
preferredNamestringDisplay name
recordingModeenumAUTOMATIC · AUTOMATIC_PAUSING_PROHIBITED · MANUAL · DISABLED. Defaults to DISABLED
canListenOwnRecordingsboolean
canDownloadOwnRecordingsboolean
canListenSkillRecordingsboolean
canDownloadSkillRecordingsboolean
canListenRoleRecordingsboolean
canDownloadRoleRecordingsboolean
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

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 an update leaves the agent's licenses alone.
  • An empty list on a XIMA agent gives it the default voice license. On a UC agent it removes
    all of its licenses.
  • Seat limits apply. If granting the licenses would exceed a limit, that agent is rejected with
    409 and the reason, and nothing else about it is written.

Skill levels

Send a skillLevels array on create or update to manage which skills the agent is in.

"skillLevels": [
  { "skillId": "ee3b0d75-9ffe-46fc-80f6-973e8c71ff8f", "skillLevel": 10, "maxSimultaneousSessions": 2 },
  { "skillName": "Sales", "skillLevel": 5, "chatEnabled": false },
  { "skillName": "Billing", "skillLevel": 0 }
]
FieldNotes
skillIdThe skill's id from GET /rest/api/v1/skills. The unambiguous option
skillNameAlternative to skillId. Used only when skillId is absent
skillLevelAbove 0 to assign, 0 to remove. Required when adding the agent to a skill they are not already on
maxSimultaneousSessionsConcurrent interactions
voiceEnabled / chatEnabled / emailEnabledPer-channel participation

Three 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. Entries that cannot be applied are skipped, not fatal. An unknown skill, a negative level,
or a new skill with no skillLevel is skipped with an IGNORED warning. The rest of the agent
still writes, and facets.skillLevels.applied says how many entries were applied.


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
rejected with 400. 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, account and busy codes, 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.

Email can be changed freely, subject to uniqueness.

Partial results

A create or update can come back PARTIAL. This means the agent's profile, licenses and skill
levels were saved, but the default outbound caller ID was not. The agent exists.

"defaultOutboundCallerId": {
  "status": "FAILED",
  "errors": [ { "field": "defaultOutboundCallerId", "reason": "\"+18015559999\" is not a caller ID this agent may use. …" } ],
  "retry": "PUT /api/v1/agents/53436b64-…/default-outbound-caller-id"
}

Do not re-send the create. The agent already exists and a second create will be rejected as a
duplicate. Fix the value and call the endpoint in retry.

Right after an agent is created, the phone system can take a moment to learn about them, and a
caller ID that should be allowed may be refused. If you see PARTIAL on a create with a caller ID
you expected to work, wait briefly and retry.

Dry runs

?dryRun=true (single) or "dryRun": true (bulk) checks every item exactly as a real write would
and reports the outcome with status: "VALID" or "REJECTED". Nothing is saved, no welcome email
is sent, and the login portal is not updated.

A new agent's default outbound caller ID cannot be checked in a dry run, because the agent does
not exist yet. It is reported as NOT_ATTEMPTED with a warning.

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; facets.welcomeEmail is FAILED and a warning says so. An administrator can
resend it from the agent list.

One write at a time

Agent and skill writes are serialised together. A write arriving while another is in progress
gets 429 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.

Reads are not affected.


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 in one call

Profile, licenses, skills and default caller ID go in one request, and the first three succeed or
fail together:

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}]}'

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 -> back off briefly and retry the same batch
    for each result:
        if status == REJECTED -> log result.errors, flag for a human
        if status == PARTIAL  -> agent exists; call facets.defaultOutboundCallerId.retry later
        if warnings non-empty -> log them

Do not send batches in parallel. One at a time, sequentially.

Handle every response properly

if HTTP >= 400:
    nothing was written; read .message
    413 -> split the batch
    429 -> back off and retry
else:
    for each result:                       # bulk can contain rejections in a 200
        REJECTED -> nothing written; see .errors and the FAILED facet
        PARTIAL  -> agent saved; default caller ID not set; use .facets.defaultOutboundCallerId.retry
        otherwise -> written. Check .warnings:
            DEFAULTED -> something was given a default value
            IGNORED   -> a field, skill entry 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
ConcurrencyOne agent or skill write at a time across the whole tenant. Others get 429
Name and extensionCannot be changed. Create a new agent instead
Skill removalRequires an explicit skillLevel: 0
UC agentsCannot be created; profile cannot be edited

Deleting agents, and assigning account codes and busy codes, are not yet available through the
API.
Use the web interface for these for now.

Web interface edits are not coordinated with the API. If an administrator saves agent or
skill settings in the web interface while your integration is writing, one of the two changes can
be lost. Avoid running 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.