Widgets
Generated from
DialerDigital/corecommit6e9f026f0a72— specopenapi/dialer-v1.yamlv0.36.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.
Embeddable agent-widget lifecycle — ddw_ browser tokens + the origin allowlist.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/widget_tokens | List widget tokens |
POST | /v1/widget_tokens | Mint a widget token |
DELETE | /v1/widget_tokens/{id} | Revoke a widget token |
POST | /v1/widget_tokens/renew | Renew the caller's own widget session |
POST | /v1/widget_tokens/introspect | The caller's own widget session context |
GET | /v1/widget_origins | List allowlisted origins |
POST | /v1/widget_origins | Add an allowlisted origin |
DELETE | /v1/widget_origins/{id} | Disable an allowlisted origin |
POST | /v1/widget_origins/{id}/enable | Re-enable an allowlisted origin |
List widget tokens
GET /v1/widget_tokensIds, agent, labels, expiry and revocation only — the token hash is never exposed.
Responses
200
The tenant's widget tokens.
| Field | Type | Required | Description |
|---|---|---|---|
widget_tokens | array of WidgetToken | yes | |
widget_tokens[].id | string (uuid) | no | |
widget_tokens[].agent_id | string (uuid) | no | |
widget_tokens[].label | string | no | |
widget_tokens[].allowed_origin_id | string (uuid) | null | no | Optional pin to one widget_origins row — narrows the EMBED origins surfaced to the frame (CSP/postMessage) to just that one; null = all the tenant's enabled embed origins. NOT the auth boundary (that is the widget-host Origin). Agent-login mints UNPINNED. |
widget_tokens[].expires_at | string (date-time) | no | |
widget_tokens[].session_expires_at | string (date-time) | no | |
widget_tokens[].revoked_at | string (date-time) | null | no | |
widget_tokens[].last_used_at | string (date-time) | null | no | |
widget_tokens[].created_at | string (date-time) | no | |
widget_tokens[].updated_at | string (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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}Mint a widget token
POST /v1/widget_tokensMints a ddw_ browser token bound to one agent. The plaintext is returned ONCE; only its sha256 is stored. TTLs are server-authoritative (never client input). Rate-limited per tenant.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string (uuid) | yes | The agent this token acts as. |
label | string | no | Free-form label (default ""). |
allowed_origin_id | string (uuid) | no | Optional — pin to a single widget_origins row (narrows the EMBED origins surfaced to the frame; NOT the auth boundary, which is the widget-host Origin). Agent-login mints UNPINNED. |
{
"agent_id": "7a3f0f7e-52a1-4f3d-9f9d-27e5a1b9c001",
"label": "crm-embed"
}Responses
201
Token minted; token is the only time the plaintext is visible.
| Field | Type | Required | Description |
|---|---|---|---|
widget_token | object (WidgetToken) | yes | A ddw_ browser widget token — the plaintext + hash are never exposed here. |
widget_token.id | string (uuid) | no | |
widget_token.agent_id | string (uuid) | no | |
widget_token.label | string | no | |
widget_token.allowed_origin_id | string (uuid) | null | no | Optional pin to one widget_origins row — narrows the EMBED origins surfaced to the frame (CSP/postMessage) to just that one; null = all the tenant's enabled embed origins. NOT the auth boundary (that is the widget-host Origin). Agent-login mints UNPINNED. |
widget_token.expires_at | string (date-time) | no | |
widget_token.session_expires_at | string (date-time) | no | |
widget_token.revoked_at | string (date-time) | null | no | |
widget_token.last_used_at | string (date-time) | null | no | |
widget_token.created_at | string (date-time) | no | |
widget_token.updated_at | string (date-time) | no | |
token | string | yes | Plaintext ddw_ token, shown exactly once. |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
{
"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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — WidgetRateLimited
The widget-token write rate brake refused this request (retryable later). code rate_limited. mint is bounded per tenant; renew per token. No Retry-After header is set.
{
"error": {
"code": "rate_limited",
"message": "widget token mint rate limit exceeded; retry later"
}
}Revoke a widget token
DELETE /v1/widget_tokens/{id}Sets revoked_at; tokens are never deleted. Idempotent.
ALSO tears the agent's SIP endpoint down (NR-02): deletes the ephemeral agentcred credential and the live usrloc binding. Without it a revoked token left the softphone able to REGISTER and carry calls until the credential's 24h autoexpire. The DB revocation is durable and happens regardless; sip_teardown reports whether the SIP half succeeded, so a 200 never implies an eviction that did not happen.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The revoked token, plus the outcome of the SIP teardown.
| Field | Type | Required | Description |
|---|---|---|---|
widget_token | object (WidgetToken) | yes | A ddw_ browser widget token — the plaintext + hash are never exposed here. |
widget_token.id | string (uuid) | no | |
widget_token.agent_id | string (uuid) | no | |
widget_token.label | string | no | |
widget_token.allowed_origin_id | string (uuid) | null | no | Optional pin to one widget_origins row — narrows the EMBED origins surfaced to the frame (CSP/postMessage) to just that one; null = all the tenant's enabled embed origins. NOT the auth boundary (that is the widget-host Origin). Agent-login mints UNPINNED. |
widget_token.expires_at | string (date-time) | no | |
widget_token.session_expires_at | string (date-time) | no | |
widget_token.revoked_at | string (date-time) | null | no | |
widget_token.last_used_at | string (date-time) | null | no | |
widget_token.created_at | string (date-time) | no | |
widget_token.updated_at | string (date-time) | no | |
sip_teardown | ok · skipped · failed | yes | Outcome 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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"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).
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}Renew the caller's own widget session
POST /v1/widget_tokens/renewSlides the AUTHENTICATED ddw_ token's expiry forward (never past the session cap). Authenticated by the ddw_ itself; operates ONLY on the caller's own token (no id from path/body). Rate-limited per token.
Responses
200
The renewed token.
| Field | Type | Required | Description |
|---|---|---|---|
widget_token | object (WidgetToken) | yes | A ddw_ browser widget token — the plaintext + hash are never exposed here. |
widget_token.id | string (uuid) | no | |
widget_token.agent_id | string (uuid) | no | |
widget_token.label | string | no | |
widget_token.allowed_origin_id | string (uuid) | null | no | Optional pin to one widget_origins row — narrows the EMBED origins surfaced to the frame (CSP/postMessage) to just that one; null = all the tenant's enabled embed origins. NOT the auth boundary (that is the widget-host Origin). Agent-login mints UNPINNED. |
widget_token.expires_at | string (date-time) | no | |
widget_token.session_expires_at | string (date-time) | no | |
widget_token.revoked_at | string (date-time) | null | no | |
widget_token.last_used_at | string (date-time) | null | no | |
widget_token.created_at | string (date-time) | no | |
widget_token.updated_at | string (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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — WidgetRateLimited
The widget-token write rate brake refused this request (retryable later). code rate_limited. mint is bounded per tenant; renew per token. No Retry-After header is set.
{
"error": {
"code": "rate_limited",
"message": "widget token mint rate limit exceeded; retry later"
}
}The caller's own widget session context
POST /v1/widget_tokens/introspectReturns the authenticated ddw_ session's tenant/agent, its allowlisted origins (for the frame CSP) and its session clocks. Authenticated by the ddw_ itself; tenant-scoped, no enumeration.
Responses
200
The caller's live session context.
| Field | Type | Required | Description |
|---|---|---|---|
widget_session | object (WidgetSession) | yes | The introspection view a frame boots from (its own live session). |
widget_session.tenant_id | string (uuid) | yes | |
widget_session.agent_id | string (uuid) | yes | |
widget_session.origins | array of string | yes | The session's enabled EMBED origins (widget_origins) for the frame's CSP / postMessage — NOT the auth boundary (the widget-host Origin). |
widget_session.expires_at | string (date-time) | yes | |
widget_session.session_expires_at | string (date-time) | yes |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}List allowlisted origins
GET /v1/widget_originsResponses
200
The tenant's allowlisted origins (enabled + disabled).
| Field | Type | Required | Description |
|---|---|---|---|
widget_origins | array of WidgetOrigin | yes | |
widget_origins[].id | string (uuid) | no | |
widget_origins[].origin | string | no | Canonical https origin (lowercase host, default :443 stripped). |
widget_origins[].label | string | no | |
widget_origins[].disabled_at | string (date-time) | null | no | Non-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame). |
widget_origins[].created_at | string (date-time) | no | |
widget_origins[].updated_at | string (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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}Add an allowlisted origin
POST /v1/widget_originsThe raw origin is normalized to canonical https form; a malformed value is a 400.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
origin | string | yes | A web origin, e.g. https://crm.example.com. |
label | string | no |
{
"origin": "https://crm.example.com"
}Responses
201
The added origin (canonical form).
| Field | Type | Required | Description |
|---|---|---|---|
widget_origin | object (WidgetOrigin) | yes | A per-tenant EMBED-allowlist web origin (canonical https form) — governs who may frame the widget (CSP frame-ancestors / postMessage), NOT the auth boundary (the widget-host Origin). |
widget_origin.id | string (uuid) | no | |
widget_origin.origin | string | no | Canonical https origin (lowercase host, default :443 stripped). |
widget_origin.label | string | no | |
widget_origin.disabled_at | string (date-time) | null | no | Non-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame). |
widget_origin.created_at | string (date-time) | no | |
widget_origin.updated_at | string (date-time) | no |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
{
"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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}422 — Unprocessable
Shape is fine, semantics are not (broken domain rule).
{
"error": {
"code": "unprocessable",
"message": "scheduled_at must be at least 10 minutes out"
}
}Disable an allowlisted origin
DELETE /v1/widget_origins/{id}Soft-disable (dropped from the embed allowlist — stops framing this CRM via frame-ancestors/postMessage; does NOT block agent login, which gates on the widget-host Origin). Origins are never deleted.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The disabled origin.
| Field | Type | Required | Description |
|---|---|---|---|
widget_origin | object (WidgetOrigin) | yes | A per-tenant EMBED-allowlist web origin (canonical https form) — governs who may frame the widget (CSP frame-ancestors / postMessage), NOT the auth boundary (the widget-host Origin). |
widget_origin.id | string (uuid) | no | |
widget_origin.origin | string | no | Canonical https origin (lowercase host, default :443 stripped). |
widget_origin.label | string | no | |
widget_origin.disabled_at | string (date-time) | null | no | Non-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame). |
widget_origin.created_at | string (date-time) | no | |
widget_origin.updated_at | string (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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"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).
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}Re-enable an allowlisted origin
POST /v1/widget_origins/{id}/enableParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The re-enabled origin.
| Field | Type | Required | Description |
|---|---|---|---|
widget_origin | object (WidgetOrigin) | yes | A per-tenant EMBED-allowlist web origin (canonical https form) — governs who may frame the widget (CSP frame-ancestors / postMessage), NOT the auth boundary (the widget-host Origin). |
widget_origin.id | string (uuid) | no | |
widget_origin.origin | string | no | Canonical https origin (lowercase host, default :443 stripped). |
widget_origin.label | string | no | |
widget_origin.disabled_at | string (date-time) | null | no | Non-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame). |
widget_origin.created_at | string (date-time) | no | |
widget_origin.updated_at | string (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).
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended.
{
"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).
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}