Skip to content

Widgets

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.

Embeddable agent-widget lifecycle — ddw_ browser tokens + the origin allowlist.

Endpoints

MethodPathSummary
GET/v1/widget_tokensList widget tokens
POST/v1/widget_tokensMint a widget token
DELETE/v1/widget_tokens/{id}Revoke a widget token
POST/v1/widget_tokens/renewRenew the caller's own widget session
POST/v1/widget_tokens/introspectThe caller's own widget session context
GET/v1/widget_originsList allowlisted origins
POST/v1/widget_originsAdd an allowlisted origin
DELETE/v1/widget_origins/{id}Disable an allowlisted origin
POST/v1/widget_origins/{id}/enableRe-enable an allowlisted origin

List widget tokens

http
GET /v1/widget_tokens

Ids, agent, labels, expiry and revocation only — the token hash is never exposed.

Responses

200

The tenant's widget tokens.

FieldTypeRequiredDescription
widget_tokensarray of WidgetTokenyes
widget_tokens[].idstring (uuid)no
widget_tokens[].agent_idstring (uuid)no
widget_tokens[].labelstringno
widget_tokens[].allowed_origin_idstring (uuid) | nullnoOptional 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_atstring (date-time)no
widget_tokens[].session_expires_atstring (date-time)no
widget_tokens[].revoked_atstring (date-time) | nullno
widget_tokens[].last_used_atstring (date-time) | nullno
widget_tokens[].created_atstring (date-time)no
widget_tokens[].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"
  }
}

Mint a widget token

http
POST /v1/widget_tokens

Mints 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)

FieldTypeRequiredDescription
agent_idstring (uuid)yesThe agent this token acts as.
labelstringnoFree-form label (default "").
allowed_origin_idstring (uuid)noOptional — 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.
json
{
  "agent_id": "7a3f0f7e-52a1-4f3d-9f9d-27e5a1b9c001",
  "label": "crm-embed"
}

Responses

201

Token minted; token is the only time the plaintext is visible.

FieldTypeRequiredDescription
widget_tokenobject (WidgetToken)yesA ddw_ browser widget token — the plaintext + hash are never exposed here.
widget_token.idstring (uuid)no
widget_token.agent_idstring (uuid)no
widget_token.labelstringno
widget_token.allowed_origin_idstring (uuid) | nullnoOptional 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_atstring (date-time)no
widget_token.session_expires_atstring (date-time)no
widget_token.revoked_atstring (date-time) | nullno
widget_token.last_used_atstring (date-time) | nullno
widget_token.created_atstring (date-time)no
widget_token.updated_atstring (date-time)no
tokenstringyesPlaintext ddw_ token, shown exactly once.

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

409 — Conflict

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

json
{
  "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.

json
{
  "error": {
    "code": "rate_limited",
    "message": "widget token mint rate limit exceeded; retry later"
  }
}

Revoke a widget token

http
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

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

Responses

200

The revoked token, plus the outcome of the SIP teardown.

FieldTypeRequiredDescription
widget_tokenobject (WidgetToken)yesA ddw_ browser widget token — the plaintext + hash are never exposed here.
widget_token.idstring (uuid)no
widget_token.agent_idstring (uuid)no
widget_token.labelstringno
widget_token.allowed_origin_idstring (uuid) | nullnoOptional 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_atstring (date-time)no
widget_token.session_expires_atstring (date-time)no
widget_token.revoked_atstring (date-time) | nullno
widget_token.last_used_atstring (date-time) | nullno
widget_token.created_atstring (date-time)no
widget_token.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"
  }
}

Renew the caller's own widget session

http
POST /v1/widget_tokens/renew

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

FieldTypeRequiredDescription
widget_tokenobject (WidgetToken)yesA ddw_ browser widget token — the plaintext + hash are never exposed here.
widget_token.idstring (uuid)no
widget_token.agent_idstring (uuid)no
widget_token.labelstringno
widget_token.allowed_origin_idstring (uuid) | nullnoOptional 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_atstring (date-time)no
widget_token.session_expires_atstring (date-time)no
widget_token.revoked_atstring (date-time) | nullno
widget_token.last_used_atstring (date-time) | nullno
widget_token.created_atstring (date-time)no
widget_token.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"
  }
}

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.

json
{
  "error": {
    "code": "rate_limited",
    "message": "widget token mint rate limit exceeded; retry later"
  }
}

The caller's own widget session context

http
POST /v1/widget_tokens/introspect

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

FieldTypeRequiredDescription
widget_sessionobject (WidgetSession)yesThe introspection view a frame boots from (its own live session).
widget_session.tenant_idstring (uuid)yes
widget_session.agent_idstring (uuid)yes
widget_session.originsarray of stringyesThe session's enabled EMBED origins (widget_origins) for the frame's CSP / postMessage — NOT the auth boundary (the widget-host Origin).
widget_session.expires_atstring (date-time)yes
widget_session.session_expires_atstring (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).

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

List allowlisted origins

http
GET /v1/widget_origins

Responses

200

The tenant's allowlisted origins (enabled + disabled).

FieldTypeRequiredDescription
widget_originsarray of WidgetOriginyes
widget_origins[].idstring (uuid)no
widget_origins[].originstringnoCanonical https origin (lowercase host, default :443 stripped).
widget_origins[].labelstringno
widget_origins[].disabled_atstring (date-time) | nullnoNon-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame).
widget_origins[].created_atstring (date-time)no
widget_origins[].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"
  }
}

Add an allowlisted origin

http
POST /v1/widget_origins

The raw origin is normalized to canonical https form; a malformed value is a 400.

Request body (JSON, required)

FieldTypeRequiredDescription
originstringyesA web origin, e.g. https://crm.example.com.
labelstringno
json
{
  "origin": "https://crm.example.com"
}

Responses

201

The added origin (canonical form).

FieldTypeRequiredDescription
widget_originobject (WidgetOrigin)yesA 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.idstring (uuid)no
widget_origin.originstringnoCanonical https origin (lowercase host, default :443 stripped).
widget_origin.labelstringno
widget_origin.disabled_atstring (date-time) | nullnoNon-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame).
widget_origin.created_atstring (date-time)no
widget_origin.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"
  }
}

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

Disable an allowlisted origin

http
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

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

Responses

200

The disabled origin.

FieldTypeRequiredDescription
widget_originobject (WidgetOrigin)yesA 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.idstring (uuid)no
widget_origin.originstringnoCanonical https origin (lowercase host, default :443 stripped).
widget_origin.labelstringno
widget_origin.disabled_atstring (date-time) | nullnoNon-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame).
widget_origin.created_atstring (date-time)no
widget_origin.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"
  }
}

Re-enable an allowlisted origin

http
POST /v1/widget_origins/{id}/enable

Parameters

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

Responses

200

The re-enabled origin.

FieldTypeRequiredDescription
widget_originobject (WidgetOrigin)yesA 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.idstring (uuid)no
widget_origin.originstringnoCanonical https origin (lowercase host, default :443 stripped).
widget_origin.labelstringno
widget_origin.disabled_atstring (date-time) | nullnoNon-null = disabled (dropped from the embed allowlist — no frame-ancestors/postMessage, never surfaced to a frame).
widget_origin.created_atstring (date-time)no
widget_origin.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"
  }
}

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