Skip to content

Agents & seats

Generated from DialerDigital/core commit 6e9f026f0a72 — spec openapi/dialer-v1.yaml v0.36.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.

Seat roster, presence, softphone checkin/checkout.

Endpoints

MethodPathSummary
GET/v1/agentsRoster + live seat presence
POST/v1/agentsCreate a seat
GET/v1/agents/{id}Fetch one agent + presence
POST/v1/agents/{id}/credentialEnable or rotate an agent's widget-login password
DELETE/v1/agents/{id}/credentialDisable an agent's widget login
POST/v1/agents/checkinSeat online + softphone bootstrap
POST/v1/agents/checkoutSeat offline (revokes the ephemeral SIP credential)
POST/v1/agents/{id}/pauseTake an available seat out of rotation (paused)
POST/v1/agents/{id}/resumeReturn a paused seat to rotation (available)
POST/v1/agents/{id}/wrap-upFinish after-call work (wrap_up -> available)
GET/v1/planned-shiftsList planned shifts for the tenant
POST/v1/planned-shiftsCreate a planned shift

Roster + live seat presence

http
GET /v1/agents

Durable roster merged with live presence (offline|available|ringing|on_call).

Responses

200

All agents with presence.

FieldTypeRequiredDescription
agentsarray of Agentyes
agents[].idstring (uuid)no
agents[].tenant_idstring (uuid)no
agents[].namestringno
agents[].emailstringno
agents[].roleagent · supervisor · adminno
agents[].statusactive · inactivenoRoster flag — NOT presence.
agents[].sip_extensionstring | nullno
agents[].device_modebrowser · externalno
agents[].login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agents[].presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agents[].created_atstring (date-time)no
agents[].updated_atstring (date-time)no

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

Create a seat

http
POST /v1/agents

Request body (JSON, required)

FieldTypeRequiredDescription
namestringyes
emailstring (email)yes
roleagent · supervisor · adminno
sip_extensionstringnoThe SIP registration identity calls bridge to. Only inert characters (digits, letters, dot, underscore, hyphen): the value is both the SIP identity and half of the media dialstring, so anything else is refused with 422 rather than diverging between the two.
json
{
  "name": "Postman Seat",
  "email": "[email protected]",
  "role": "agent",
  "sip_extension": "1009"
}

Responses

201

Created seat.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no

400 — BadRequest

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

409 — Conflict

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

Fetch one agent + presence

http
GET /v1/agents/{id}

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200

The agent.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

Enable or rotate an agent's widget-login password

http
POST /v1/agents/{id}/credential

Sets (or rotates) the agent's widget-login credential so they can sign into the widget with email + password (POST /v1/auth/agent-login). Tenant scope (a dd_ key or ddu_ session; a ddw_ is 403). The plaintext password is body-only — never persisted (PBKDF2-SHA512 at rest) or echoed back. A ROTATION revokes the agent's live ddw_ (the re-key eviction) AND tears down the SIP endpoint minted under that session (NR-02), reported in sip_teardown.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Request body (JSON, required)

FieldTypeRequiredDescription
passwordstring (password)yes

Responses

200

The agent (now login_enabled true), plus the SIP teardown outcome.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no
sip_teardownok · skipped · failedyesOutcome of the SIP teardown that accompanies a revocation (NR-02): the deletion of the agent's ephemeral agentcred credential and its live usrloc binding in Kamailio. * ok — credential and binding removed; the softphone can no longer register. * skipped — nothing to remove: the agent has no SIP extension, SBC provisioning is not configured in this environment, or the agent still holds a VIGENT session (see below) so this revocation left no access open. * failed — the DB revocation stands, but the SIP credential is STILL LIVE and will remain so until its 24h autoexpire. Treat as an open access path: retry, or evict the agent by other means. It is a field and not an error status on purpose: the revocation itself is durable and did happen, so a 5xx would be a lie in the other direction. What must never happen is a silent 200 implying an eviction that did not occur. The teardown follows the TRANSITION, not the agent. Revoking a token id that was already revoked — an id a client can legitimately hold, since GET /v1/widget_tokens lists revoked rows, and since minting supersedes the previous token — must NOT evict, because the agent's SUCCESSOR session would be the one cut. The teardown therefore runs only when the agent is left with no vigent token; otherwise it reports skipped. This is what keeps failed's retry advice usable: an already-revoked token with no live successor still evicts on a retried DELETE, so retrying is a real remedy and not a no-op.

400 — BadRequest

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

Disable an agent's widget login

http
DELETE /v1/agents/{id}/credential

Clears the agent's widget-login password (back to login_enabled: false) and revokes any live ddw_ the agent holds, so the disable takes effect at once — INCLUDING the SIP endpoint (NR-02), reported in sip_teardown. Without that half, a disabled agent kept dialling over SIP until the credential's 24h autoexpire. The seat itself is untouched. Idempotent. Tenant scope (a ddw_ is 403).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200

The agent (now login_enabled false), plus the SIP teardown outcome.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no
sip_teardownok · skipped · failedyesOutcome of the SIP teardown that accompanies a revocation (NR-02): the deletion of the agent's ephemeral agentcred credential and its live usrloc binding in Kamailio. * ok — credential and binding removed; the softphone can no longer register. * skipped — nothing to remove: the agent has no SIP extension, SBC provisioning is not configured in this environment, or the agent still holds a VIGENT session (see below) so this revocation left no access open. * failed — the DB revocation stands, but the SIP credential is STILL LIVE and will remain so until its 24h autoexpire. Treat as an open access path: retry, or evict the agent by other means. It is a field and not an error status on purpose: the revocation itself is durable and did happen, so a 5xx would be a lie in the other direction. What must never happen is a silent 200 implying an eviction that did not occur. The teardown follows the TRANSITION, not the agent. Revoking a token id that was already revoked — an id a client can legitimately hold, since GET /v1/widget_tokens lists revoked rows, and since minting supersedes the previous token — must NOT evict, because the agent's SUCCESSOR session would be the one cut. The teardown therefore runs only when the agent is left with no vigent token; otherwise it reports skipped. This is what keeps failed's retry advice usable: an already-revoked token with no live successor still evicts on a retried DELETE, so retrying is a real remedy and not a no-op.

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

Seat online + softphone bootstrap

http
POST /v1/agents/checkin

Seat goes available. Optional device_mode (browser|external) persists on the seat and refines the campaign bridge hunt; omitted keeps the stored value (re-checkin stays idempotent). When SBC provisioning is enabled (KAMAILIO_RPC_URL; always on in prod) the response carries the FULL softphone bootstrap — an ephemeral SIP password minted per checkin, provisioned into Kamailio's agentcred htable (the ONLY place it exists; 24h autoexpire). Re-checkin reissues. With provisioning disabled the sip key is simply absent.

Two failures AFTER the seat is opened answer 503 and revert it, so the seat is never left claiming an agent the caller cannot reach. Branch on code: sbc_unavailable when the SBC RPC fails — credentials that cannot register are never returned — and presence_unavailable when the durable seat-presence write fails (core#557).

Request body (JSON, required)

FieldTypeRequiredDescription
sip_extensionstringyes
device_modebrowser · externalnobrowser = kamailio-first WSS softphone; external = straight to the FS registrar.
json
{
  "sip_extension": "1001",
  "device_mode": "external"
}

Responses

200

Seat online (+ softphone bootstrap when SBC provisioning is on).

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no
sipobject (SipBootstrap)noSoftphone bootstrap issued at checkin when SBC provisioning is enabled. The ephemeral password lives ONLY in Kamailio's agentcred htable — never in Postgres, never logged. Absent entirely when provisioning is disabled.
sip.extensionstringnoDigest username (bare AOR).
sip.passwordstringnoEphemeral, minted per checkin, NEVER persisted.
sip.wss_urlstringnoSOFTPHONE_WSS_URL.
sip.domainstringnoSOFTPHONE_SIP_DOMAIN (From URI).
sip.expires_atstring (date-time)noWhen this credential stops working. The htable autoexpire horizon (24h) CAPPED by the agent's widget session, when one STILL governs it: that session's absolute cap is at most 12h and the server evicts the credential the moment it lapses, so the earlier of the two is the only horizon a client can plan a renewal against. Always in the future — a session whose cap has already lapsed reports the flat horizon, never a past instant for a credential just issued.
json
{
  "agent": {
    "id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
    "tenant_id": "0d4f4f9e-1f2a-4b53-9d3c-8a5e2f7b1c10",
    "name": "Postman Seat",
    "email": "[email protected]",
    "role": "agent",
    "status": "active",
    "sip_extension": "1001",
    "device_mode": "external",
    "presence": "available",
    "created_at": "2026-06-01T12:00:00Z",
    "updated_at": "2026-07-05T10:00:00Z"
  },
  "sip": {
    "extension": "1001",
    "password": "ephemeral-example-not-real",
    "wss_url": "wss://127.0.0.1:8443",
    "domain": "sip.usa.dialerdigital.com",
    "expires_at": "2026-07-06T10:00:00Z"
  }
}

400 — BadRequest

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

422 — Unprocessable

Shape is fine, semantics are not (broken domain rule).

json
{
  "error": {
    "code": "unprocessable",
    "message": "scheduled_at must be at least 10 minutes out"
  }
}

503 — CheckinUnavailable

Checkin refused fail-closed AFTER the seat was opened, so the seat was reverted and nothing durable is left claiming the agent is available. Retry. TWO causes, told apart by code (ErrorEnvelope says to branch on it, and a client that only knows one of them will silently do nothing for the other):

  • sbc_unavailable — the SBC RPC failed. Credentials that cannot register are never returned.
  • presence_unavailable — the durable seat-presence write failed (core#557). It is not cosmetic: that column is what SessionCapSweeper reads to tell this session apart from the one it is tearing down, so a checkin that answered 200 without it hands out a live SIP credential over a seat the sweeper may take back.

This response replaced the former SbcUnavailable, whose name promised the single cause it had when the checkin only had one.

sbc_unavailable

json
{
  "error": {
    "code": "sbc_unavailable",
    "message": "softphone provisioning unavailable, retry"
  }
}

presence_unavailable

json
{
  "error": {
    "code": "presence_unavailable",
    "message": "seat presence could not be recorded, retry"
  }
}

Seat offline (revokes the ephemeral SIP credential)

http
POST /v1/agents/checkout

Only an available seat may leave (409 otherwise, fail-closed — a seat on a call cannot vanish). Revokes the ephemeral SIP credential (idempotent htable delete; the 24h autoexpire is the backstop for a missed revoke).

Request body (JSON, required)

FieldTypeRequiredDescription
sip_extensionstringyes
json
{
  "sip_extension": "1001"
}

Responses

200

Seat offline.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no

400 — BadRequest

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

Take an available seat out of rotation (paused)

http
POST /v1/agents/{id}/pause

Moves an available (or wrap_up) seat to paused with a reason from a CLOSED set; a paused seat is never handed a call and may still check out. Tenant scope only (a ddw_ agent token is 403 by the widget allowlist). 409 when the agent is not checked in, is reserved / on a call, or is already paused (fail-closed: nothing is coerced). The seat is in-memory state; whether the seat stays out of rotation across a restart depends on the tenant's agent_session_journal_enabled flag (ADR #101 D10).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Request body (JSON, required)

FieldTypeRequiredDescription
reasonmanual · break · lunch · training · meetingyes
json
{
  "reason": "lunch"
}

Responses

200

Seat paused; agent.presence is read back from the seat.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no

400 — BadRequest

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

Return a paused seat to rotation (available)

http
POST /v1/agents/{id}/resume

Moves a paused seat back to available, at the BACK of the reservation queue. Tenant scope only. 409 when the agent is not checked in or the seat is not paused.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200

Seat available again; agent.presence is read back from the seat.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

Finish after-call work (wrap_up -> available)

http
POST /v1/agents/{id}/wrap-up

The agent is done with after-call work: moves a wrap_up seat back to available, at the BACK of the reservation queue, and drops the reservation token the call left on the seat. Tenant scope only. 409 when the agent is not checked in or the seat is not in wrap_up.

There is NO verb that ENTERS wrap_up: the seat FSM enters it only on the hangup of an ANSWERED call, and only when the tenant's agent_wrap_up_seconds cap is above 0 (ADR #101 D5/D8). The cap is the other exit — the seat returns to available on its own when it expires — and POST /v1/agents/{id}/pause is the third (an agent may close its ACW with a pause).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200

After-call work finished; agent.presence is read back from the seat.

FieldTypeRequiredDescription
agentobject (Agent)yes
agent.idstring (uuid)no
agent.tenant_idstring (uuid)no
agent.namestringno
agent.emailstringno
agent.roleagent · supervisor · adminno
agent.statusactive · inactivenoRoster flag — NOT presence.
agent.sip_extensionstring | nullno
agent.device_modebrowser · externalno
agent.login_enabledbooleannoWidget self-login state — true iff a password credential is set. The password hash itself is never exposed.
agent.presenceoffline · available · ringing · on_call · wrap_up · pausednoLive seat presence from the in-memory AgentFSM. wrap_up is after-call work (entered on hangup when the tenant's agent_wrap_up_seconds cap is above 0); paused is a seat taken out of rotation with a reason (ADR #101 D1, MT-ROAD-R11).
agent.created_atstring (date-time)no
agent.updated_atstring (date-time)no

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

List planned shifts for the tenant

http
GET /v1/planned-shifts

Responses

200

List of planned shifts

FieldTypeRequiredDescription
shiftsarray of PlannedShiftyes
shifts[].idstring (uuid)yes
shifts[].agent_idstring (uuid)yes
shifts[].start_timestring (date-time)yes
shifts[].end_timestring (date-time)yes
shifts[].planned_stateavailable · ringing · on_call · wrap_up · pausedyes

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

Create a planned shift

http
POST /v1/planned-shifts

Request body (JSON, required)

FieldTypeRequiredDescription
agent_idstring (uuid)yes
start_timestring (date-time)yes
end_timestring (date-time)yes
planned_stateavailable · ringing · on_call · wrap_up · pausedno

Responses

201

Planned shift created.

FieldTypeRequiredDescription
idstring (uuid)yes

400 — BadRequest

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden

The tenant is suspended.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.