Agent Management APIs
Create, read and update agents programmatically: profile, licenses, skill levels and default outbound caller ID.
Contents
- Getting started
- How responses work
- Endpoints
- Agent fields reference
- Skill levels
- Behaviour you need to understand
- Building an integration
- Limits and known gaps
Getting started
Base URL
https://<your-tenant>/rest/api/v1/agents
Replace <your-tenant> with your own hostname. Note the /rest prefix — it is easy to leave out.
Authentication
Every request needs a Service User token:
Authorization: Bearer <your-token>
Create a Service User in the web interface under API Enablement → Service Users, then copy its token.
Writes require a Service User with full access. Any valid token can read agents. Creating
and editing agents, and setting a default outbound caller ID, require a Service User whose
access is unrestricted; a restricted one receives403. Every change is recorded in the
settings audit log against the Service User that made it.
Content type
All request bodies are JSON. Send Content-Type: application/json.
Your first call
curl -s "https://<your-tenant>/rest/api/v1/agents" \
-H "Authorization: Bearer $TOKEN"Two kinds of agent
| Type | What it is | What you can do |
|---|---|---|
XIMA | A browser (WebRTC) agent created in Xima | Create, read, edit everything |
UC | An agent that comes from your phone system | Read; 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"
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:
| Facet | Covers |
|---|---|
profile | Name, extension, email, recording mode and permissions, connected numbers, no-answer overflow |
licensing | The agent's licenses |
skillLevels | The agent's skill memberships |
defaultOutboundCallerId | The caller ID the agent dials out with by default |
welcomeEmail | The welcome email sent to a new agent |
Each facet has a status:
status | Meaning |
|---|---|
APPLIED | Written |
FAILED | Not written. errors says why |
NOT_REQUESTED | Your request did not ask to change it |
NOT_ATTEMPTED | Not 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
status | Meaning |
|---|---|
CREATED | A new agent was created |
UPDATED | An existing agent was changed |
PARTIAL | The agent was saved, but its default outbound caller ID was not. See Partial results |
REJECTED | Nothing was written for this agent. See errors |
VALID | Dry 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."
}action | Meaning |
|---|---|
DEFAULTED | You omitted or sent something that could not be used, so a default was applied. resolvedTo says what |
IGNORED | The 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
| Method | Path | Purpose |
|---|---|---|
GET | /rest/api/v1/agents | List licensed agents |
GET | /rest/api/v1/agents/{id} | One agent in full |
POST | /rest/api/v1/agents/create | Create an agent |
POST | /rest/api/v1/agents/create/bulk | Create many agents at once |
PATCH | /rest/api/v1/agents/{id} | Update an agent |
PATCH | /rest/api/v1/agents | Update many agents at once |
GET | /rest/api/v1/agents/{id}/default-outbound-caller-id | Read the default outbound caller ID |
PUT | /rest/api/v1/agents/{id}/default-outbound-caller-id | Set it |
DELETE | /rest/api/v1/agents/{id}/default-outbound-caller-id | Clear it |
Identifying an agent
{id} can be either of:
- The agent's id — a UUID, for
XIMAagents. - The agent's key —
name(extension), exactly as the list returns it inagent, for example
Jane Doe(101). Works for both types, and is the only way to address aUCagent.
URL-encode it (Jane%20Doe(101)).
A name or an extension on its own is never accepted: on many tenants neither is unique, and the
API will not guess which agent you meant. Use the list endpoint to map your own identifiers.
List agents
GET /rest/api/v1/agents
GET /rest/api/v1/agents?type=XIMA
Response 200
{
"agents": [
{ "id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21", "agent": "Jane Doe(101)", "name": "Jane Doe",
"extension": "101", "type": "XIMA", "email": "[email protected]" },
{ "agent": "Desk Phone(2001)", "name": "Desk Phone", "extension": "2001", "type": "UC",
"email": "[email protected]" }
]
}Lists licensed agents. type filters to XIMA or UC. id is omitted for UC agents.
| Status | Meaning |
|---|---|
200 | Success |
401 | Missing or invalid token |
Get one agent
GET /rest/api/v1/agents/{id}
Response 200
{
"id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21",
"agent": "Jane Doe(101)",
"name": "Jane Doe",
"extension": "101",
"type": "XIMA",
"email": "[email protected]",
"profile": {
"id": "53436b64-2d5c-4f0e-9a51-6c3b0f8e7d21",
"name": "Jane Doe",
"preferredName": "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.
profileis absent forUCagents.skillLevelslists only skills the agent is on (skill level above 0).defaultOutboundCallerIdis absent when the agent has none.
| Status | Meaning |
|---|---|
200 | Success |
401 | Missing or invalid token |
404 | No 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
}| Field | Notes |
|---|---|
profile | See Profile fields. Omitted fields take the same defaults the web interface uses for a new agent |
licenses | See Licenses. If omitted, the agent gets the license the web interface gives every new agent, and a DEFAULTED warning says so |
skillLevels | See Skill levels |
defaultOutboundCallerId | Must be a caller ID this agent is allowed to use |
sendWelcomeEmail | Defaults 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.
| Status | Meaning |
|---|---|
201 | Created — including PARTIAL, where the agent exists but its default outbound caller ID was not set |
200 | Dry run |
400 | profile 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) |
401 | Missing or invalid token |
403 | The Service User does not have full access |
409 | Name, 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) |
429 | Another 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.
| Status | Meaning |
|---|---|
200 | Processed. Check each status and the summary — individual agents may still have been rejected |
400 | No agents supplied |
401 | Missing or invalid token |
403 | The Service User does not have full access |
413 | More than 500 items. Split into smaller batches |
429 | Another 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 }
}| Status | Meaning |
|---|---|
200 | Updated (or dry run) |
400 | A 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 |
401 | Missing or invalid token |
403 | The Service User does not have full access |
404 | No agent with that id or key |
409 | The email is already in use, or the licenses could not be granted |
429 | Another 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.
| Status | Meaning |
|---|---|
200 | Processed. Check each status and the summary |
400 | No agents supplied |
401 | Missing or invalid token |
403 | The Service User does not have full access |
413 | More than 500 items. Split into smaller batches |
429 | Another 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.
| Status | Meaning |
|---|---|
200 / 204 | Success |
400 | The caller ID is not one this agent may use, or is blank |
401 | Missing or invalid token |
403 | The Service User does not have full access (PUT, DELETE) |
404 | No 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
| Field | Create | Update | Notes |
|---|---|---|---|
id | — | Bulk only (required) | The agent's id, or key for a UC agent |
profile | Required | Optional | XIMA agents only |
licenses | Optional | Optional | |
skillLevels | Optional | Optional | |
defaultOutboundCallerId | Optional | Optional | null clears it on an update |
sendWelcomeEmail | Optional | — | Defaults to true |
agent, name, extension, type and email are accepted on an update so a fetched agent can be
sent straight back. They are read-only; if one differs from the stored value you get an
IGNORED warning and nothing changes. To change the email, use profile.emailAddress.
Profile fields
| Field | Type | Notes |
|---|---|---|
name | string | Required on create. Unique across agents. Cannot be changed |
extension | string | Required on create. Unique across agents. Cannot be changed |
emailAddress | string | Required on create. Unique across agents. Can be changed |
preferredName | string | Display name |
recordingMode | enum | AUTOMATIC · AUTOMATIC_PAUSING_PROHIBITED · MANUAL · DISABLED. Defaults to DISABLED |
canListenOwnRecordings | boolean | |
canDownloadOwnRecordings | boolean | |
canListenSkillRecordings | boolean | |
canDownloadSkillRecordings | boolean | |
canListenRoleRecordings | boolean | |
canDownloadRoleRecordings | boolean | |
connectedNumbers | array | Phones the agent can take calls on: name, number, disablePress1ToAnswer, disableCallLink, enableSipSubscribe |
noAnswerOverflow | object | Where an unanswered direct call goes: destinationType (IVR · SKILL · AGENT · DIALABLE_NUMBER · VOICEMAIL_EXTENSION) plus destination |
Licenses
licenses is the agent's complete set of licenses, using the same names the Agent Licensing
screen uses. Fetch an existing agent to see the names your tenant uses. Common ones:
| License | Meaning |
|---|---|
CCAAS_VOICE | Contact center voice agent |
CCAAS_REALTIME | Realtime-only seat. Replaces voice and its add-ons |
CCAAS_VOICE.CCAAS_WEB_CHAT_ADDON | Chat add-on. Requires CCAAS_VOICE |
CCAAS_VOICE.CCAAS_EMAIL_ADDON | Email add-on. Requires CCAAS_VOICE |
CCAAS_CRM | CRM. Requires the agent to have an email |
CCAAS_TRANSCRIPTION | Transcription |
CCAAS_QA_EVALUATION | QA evaluation |
Tenants on UCaaS licensing use UCAAS_* names instead (for example UCAAS_VOICE_AGENT,
UCAAS_MULTI_AGENT).
- Sending
licensesreplaces that agent's licenses with the list. Other agents are not
touched. - Omitting
licenseson an update leaves the agent's licenses alone. - An empty list on a
XIMAagent gives it the default voice license. On aUCagent it removes
all of its licenses. - Seat limits apply. If granting the licenses would exceed a limit, that agent is rejected with
409and 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 }
]| Field | Notes |
|---|---|
skillId | The skill's id from GET /rest/api/v1/skills. The unambiguous option |
skillName | Alternative to skillId. Used only when skillId is absent |
skillLevel | Above 0 to assign, 0 to remove. Required when adding the agent to a skill they are not already on |
maxSimultaneousSessions | Concurrent interactions |
voiceEnabled / chatEnabled / emailEnabled | Per-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 size | 500 agents per request. More returns 413 |
| Concurrency | One agent or skill write at a time across the whole tenant. Others get 429 |
| Name and extension | Cannot be changed. Create a new agent instead |
| Skill removal | Requires an explicit skillLevel: 0 |
| UC agents | Cannot 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.
Updated about 2 hours ago
