Appearance
Roadmap
Español: Hoja de ruta.
Assume nothing here is served unless the entry says shipped or live
This page is the register of designed-but-unbuilt surfaces, kept so a design is never confused with the live API. If an entry is not explicitly marked shipped or live, the current core does not serve it — do not build against it yet. Entries that were designed here and delivered afterwards stay on the page, marked and linked to their reference, so the record of what was promised and what landed is one page. The implemented surface is exactly what REST API and WebSocket events describe.
Outbound webhooks
Server-to-server push of platform facts to your endpoints — shipped in core (MT-CTI-04, phases 1-4): the endpoint registry /v1/webhooks, an outbox written in the same transaction as the fact, a dispatcher with HMAC signatures, a capped retry ladder and an append-only delivery log. The integration guide is Outbound webhooks (CRM integration).
It has been in a published core release since v0.36.0 and is in the vendored spec this site generates its API Reference from, which is the authority on request and response shapes; the guide stays the integration walkthrough.
Not to be confused with the inbound SMS webhooks, which are live: the platform hosts signed endpoints that SMS providers call with delivery receipts and consumer replies — see Webhooks in the API Reference.
What the design preview on this page promised, and what was actually built — the record of both, since that is what this page is for:
| Promised here | Built |
|---|---|
Payloads: dialer.v1 messages as protojson (CallEvent, CallDetailRecord, ComplianceDecision) | A v1 envelope {event, event_id, occurred_at, tenant_id, data} whose data is the public /v1 projection of the resource — the same shape its GET answers, not protojson. Compliance decisions are not an event; the six-name catalog is call.ended, call.answered, promise.recorded, sms.received, sms.optout, recording.available. |
Routing header X-DD-Event-Type | X-DD-Event |
Unique X-DD-Delivery-ID | X-DD-Delivery-Id — the receiver's idempotency key, as promised |
X-DD-Signature: t=<unix>,v1=<hex> over "<t>.<raw body>", constant-time, ~5-minute tolerance | As promised, plus X-DD-Signature-Previous during a 24 h rotation window |
Respond 2xx within 10 s | 15 s for the whole request, 5 s of it for the connect |
| Retries with exponential backoff; at-least-once; no cross-delivery ordering | As promised. The ladder is capped and bounded by a maximum attempt count, then dead-letters; dedupe is on X-DD-Delivery-Id (delivery) and event_id (fact), not on callId/decisionId. |
| Self-service endpoint CRUD + delivery log per tenant | As promised: CRUD, secret rotation, GET /v1/webhooks/{id}/deliveries with every attempt, and a manual redelivery that appends. |
Protojson alignment
The contracts repo defines dialer.v1 (see Contracts), but the REST API currently speaks its own snake_case JSON with lowercase dispositions (answered_human, …), not protojson with enum names (DISPOSITION_ANSWERED_HUMAN). Alignment work (the CDR item is delivered in core main; webhooks and the rest of the REST surface keep their own shape):
- Shipped in core
v0.87.0(core#1472), and in the API Reference:GET /v1/reports/cdr, the CDR as canonicaldialer.v1.CallDetailRecordprotojson (camelCase, enum names, RFC 3339 timestamps, proto3 defaults omitted), with its field and enum names checked by core's tests against the.protoat the pinnedcontractstag — seeGET /v1/reports/cdr. It answers the same filters, keyset pagination and tenant isolation as/v1/call_attempts, which keeps its snake_case shape. One row of the append-onlycall_attemptstable is one record, so a correction is its own record; a disposition outside the contract's closed enum travels inlabels.disposition_code. - Shipped in core
v0.86.0(published asv0.86.0-prod-0.1), and in the API Reference:GET /v1/dispositions, the catalog of thedialer.v1.Dispositionenum with each lowercase REST value, its enum name, its enum number and a description. It lists the contract's closed set and leaves outunspecified;call_attempts.dispositionstays an open vocabulary and can carry codes that are not in the catalog (abandoned,delivered,ai_conversation, …). - Shipped: the mapping of the engine's lowercase dispositions onto the Protobuf enum naming is one table in code, derived from the enum mirror that CI compares with the pinned
contractstag; a new enum value without its REST pair turns CI red, and a source check keeps the contract values from being typed as loose strings elsewhere. No third shape was invented: REST keeps the lowercase value (as in the CDR query) and the catalog carries the enum name beside it.
Compliance reporting endpoints
- Collection-notice attestation is shipped in core
v0.88.0(core#1587, ADR #211): the agent appends what they declared for one call,GET /v1/call_attemptsand the attempts CSV expose one of five states, and defense packet version 2 carries every declaration. The off-by-default, founder-owned enforcement key can require the declaration before a contacted debt call closes. See Collection-notice attestation. - Durable blocked-dial reporting shipped under different paths:
GET /v1/stats/blocked(by-gate counts + daily series over the append-onlygate_blocksevidence) and the packagedGET /v1/reports/violations-prevented(JSON/CSV). The proposedGET /v1/reports/compliance/blockedname is superseded by these. - Shipped in core
v0.86.0(published asv0.86.0-prod-0.1), and in the API Reference:GET /v1/consumers/{id}, the debtor card. The consumer is identified by theconsumer_refyour CMS sent on import (the platform has no separate consumers record; it is the key Reg F already counts under). The card carries the consumer's debts, each with its Reg F counter and its own next permitted window, the cease-and-desist records, DNC counts per list (never the numbers) and the next window for the consumer as a whole in UTC and in the destination's local time. Everything is computed when you ask, from the append-only call, conversation, cease-and-desist and DNC records, by running the same quiet-hours, state-matrix and Reg F checks that gate a dial;not_evaluated_gatesnames what the window does not cover (consent, AI and artificial-voice disclosure, campaign state, DNC freshness). When the card cannot decide it saysundecidableand why; it never answers "call now" by default. The sealedGET /v1/debts/{id}/defense-packetremains the litigation bundle. - Defense-packet archives are shipped in core
v0.88.0(core#1578): an explicitPOSTarchives the ZIP with store-reported retention and an RFC 3161 time-stamp; the twoGEToperations list attestations and return the offline verification kit. The integration guide distinguishesCOMPLIANCE/GOVERNANCEobject retention frombucket_lockconfigured retention and walks a third party throughopenssl ts -verify. (Contract: thearchiveDefensePacket,listDefensePacketArchivesandgetDefensePacketAttestationoperations; archive fields: theDefensePacketArchiveschema.)
User accounts, roles, SSO, MFA
- Dashboard user accounts — shipped: email + password login minting a revocable
ddu_session, logout, password reset, and user management (create, disable, re-enable — rows are audit evidence and never delete). Machine-to-machine callers still use per-tenant API keys, and there is noX-Tenant-IDimpersonation. - Roles — shipped, but deliberately small: a two-value
roleon each user (member/account_admin). Only anaccount_adminmay write the empresa-wide rows sibling sedes inherit; amembergets403. The role is read-only over the API — promotion is an operator gesture, not a request. There is no general role/permission system. - SSO (SAML) — shipped in core
v0.86.0(published asv0.86.0-prod-0.1); its routes are in the API Reference (the contract is described in Authentication). SP-initiated login through the tenant's own IdP, configured by anaccount_admin, with strict assertion validation, no just-in-time provisioning and a two-step exchange for theddu_session. It is inert in a cell that has no SP base URL. Requiring SSO for a tenant is a per-tenant setting (sso_required_enforced) that ships off; turning it on is an enforcement decision. The dashboard's "Sign in with SSO" button and IdP configuration screen ship in dashboard v0.41.0 (dashboard#532, dashboard#520). Single logout is shipped in corev0.88.0(core#1581): a signedLogoutRequestfrom the tenant's IdP, posted to/v1/auth/sso/{tenant_id}/slo, revokes everyddu_session of the person itsNameIDnames (it fails closed with a uniform400, and an unknownNameIDanswers exactly like a known one), andPOST /v1/auth/logoutof an SSO-born session also returns the IdP's logout URL when the IdP published one; the local revocation never depends on the IdP. IdP-initiated login is deliberately not supported: an unsolicited response would be a login-CSRF and replay vector, which the two-step exchange closes, so a sign-in always starts at the platform. - MFA (TOTP + recovery codes) — shipped in core
v0.86.0(published asv0.86.0-prod-0.1); its routes are in the API Reference (the contract is described in Authentication). Optional per user: a person enrols an authenticator app (RFC 6238 TOTP) and gets ten single-use recovery codes, shown once; with a factor active,POST /v1/auth/loginreturns a one-shotchallengeinstead of a session and theddu_token is minted only atPOST /v1/auth/mfa/verify. Tenant API keys (dd_) are not affected. Requiring a second factor for every user of a tenant is a per-tenant setting (mfa_required_enforced) that ships off; turning it on is an enforcement decision. The administrative recovery path is shipped in corev0.88.0(core#1580): anaccount_admincan remove another person's factor after recording a reason, which invalidates the recovery codes, revokes the person's live sessions and appends an audit event; a tenant that requires MFA sends the person through forced enrolment on the next login. SMS remains outside the supported factors.
Agent workspace surfaces
GET /v1/agent/queue— shipped in corev0.86.0(published asv0.86.0-prod-0.1), and in the API Reference. It returns what the dialer has in front of the seat of the calling token: the seat's presence, the live call, the callbacks that seat scheduled and that have not run yet, and the live inbound offers addressed to it (empty unless the tenant's inbound queue stream is on). It is a read that composes four existing sources, not a queue model of its own, and it carries ids, dates and priority only: no phone numbers and no notes. It needs a login session (ddu_) bound to an agent seat; a tenant API key (dd_) has no seat and gets403, and a widget token (ddw_) is outside the route allowlist. There is no per-agent preview queue to show (in preview mode the account is chosen when the call is dialed, and no row reserves it) and callbacks are not assigned to agents (a callback's agent is its author), so neither appears.PATCH /v1/agents/{id}/status— shipped in the same release as above. It is a façade over the existing agent state machine, not a second presence model:{"status": "available" | "paused" | "offline"}is translated to the existing verbs (unpauseor end of wrap-up,pausewith a requiredreasonfrom the closed setmanual,break,lunch,training,meeting, and checkout). A transition the state machine does not admit answers409with the seat's current state inerror.current_state;offline → availableis one of them, because checkin stays the verb that decides capacity and SIP registration. Any otherstatus, orpausedwithout a validreason, is400, an agent of another tenant is404, and a widget token is403. Presence also changes via checkin/checkout and the dialer itself.- A
wrap_uppresence state — shipped in core: an answered call can move an agent intowrap_upwhen the ACW cap is positive, andPOST /v1/agents/{id}/wrap-upexplicitly closes that ACW state. The governedagent_wrap_up_secondssetting defaults to0, so ACW is available but off by default; enablement is a platform-operator decision. See core ADR #101, the agent endpoints, and core v0.85.0. The capability remains incore@main. - Voice inside the agent widget — built, and armed per environment by the platform operator. The frame registers a softphone of its own with a SIP credential minted for that one agent session; where the lane is not armed the call keeps ringing on the seat device and the frame says so. Your
<iframe>needsallow="microphone; autoplay"either way — see Agent widget (embed). - A dialpad (typing a number) inside the widget — shipped in core
v0.86.0(published asv0.86.0-prod-0.1) and in the reference frame, and in the API Reference.POST /v1/dial/manualtakes an E.164 number the agent typed and resolves it against the phones of the tenant's own accounts: exactly one account → the call goes through the same compliance gates as a preview dial and is attributed to that account; none, several, no campaign or a quarantined number →422and no call. A number with no account is not dialable from the widget. What it does, and its outcomes, are in Dialing a typed number (dialpad). Preview dial (POST /v1/campaigns/{id}/dial, on a call row that already carries a campaign and an account) keeps working unchanged.
Telephony control & AMD
- Supervisor audio (listen / whisper / barge / take-over) — shipped (telephony control v0.4): the supervision endpoints are live, the Live Monitor drives them, and
supervision.started/endedstream on the live floor. - AMD (answering-machine detection) — shipped for campaigns created with
amd_enabled. When the call is answered, the platform listens for a voicemail beep for 5 seconds. A beep inside that window is amachineverdict, and the call closes asanswered_machine, withamd_verdict: "machine"on its row inGET /v1/call_attempts; see answered calls with no bridge. If no beep is heard in the window, the verdict ishuman. That bias is on purpose: a mailbox that beeps later is treated as a person, rather than risk hanging up on one.- A machine is not a delivery, and it is not dialled again the same day. If no active retry rule of yours for
answered_machineapplies to the call, the platform applies a default one: the account is not offered again until 08:00 of the next day in the consumer's local time (in every timezone its numbers map to), so at most one voicemail per account per day. Without it, a campaign kept calling the number until the Reg F 7-in-7 limit blocked it (measured on 2026-10-01: 7 calls in 20 seconds). A retry rule of your own foranswered_machine, for your whole account or for the call's campaign, replaces the default; the recommended cadences include one that reschedules a machine for 24 hours later. - The
amd_verdictsrewind filter matches the latest placed attempt and acceptsunspecified | human | machine | not_sure; an unknown value returns422fail-closed. New verdicts also appear ascall.amd_verdicton the live event stream. It was merged in core#1458 and shipped in corev0.86.0(published asv0.86.0-prod-0.1).
- A machine is not a delivery, and it is not dialled again the same day. If no active retry rule of yours for
- Recordings retrieval API — shipped: listing, per-recording fetch, signed playback URLs and legal hold are live.
- Per-day schedule grid for campaigns — shipped in core: campaigns support a nullable weekly schedule (
mon…sun) that only narrows the campaign-wide and quiet-hours windows; an absent schedule preserves the previous behavior, and malformed or empty schedules fail closed. The field is accepted and returned by the campaign API, with422for an unreadable or out-of-day window. See core ADR #163, the campaign API, and core v0.85.0. It remains incore@main. - Campaign calendar start/end — shipped in core
v0.87.0: nullablestarts_atandends_atare wall-clock values in the campaign'sdefault_timezone; the off-by-defaultcampaign_window_enabledoperational flag gates the scheduler that starts eligible voice campaigns and stops them atends_at. See Calendar start and end and core ADR #206. - Inbound calls on a company-wide BYOC trunk — shipped in core
v0.88.0(core#1565):scope: "empresa"acceptssource_ips, the source identifies the account and the dialed DID selects the destination office. Registration and revoke are restricted toaccount_admin; each source address remains unique across the platform. See Inbound calls on a company-wide BYOC trunk and core ADR #203.
Import & data plumbing
- CSV multipart upload for
/v1/debts/import— shipped in corev0.86.0(published asv0.86.0-prod-0.1), and in the API Reference. The form accepts an in-memorytext/csvfile, an explicit target-to-header mapping andcomma/semicolon/tab; UTF-8 (optional BOM), default limits of 4 MB and 10,000 rows (per-tenant settings) fail closed before import. Both forms enter the same per-row validation and normalization pipeline. - Reg F migration seeds on
/v1/debts/import—last_conversation_atshipped in corev0.90.0(core#1633, ADR #212; JSON since core#1626, CSV since core#1631), and in the API Reference. A migrated portfolio can declare both its prior attempts (prior_attempts_7d, ADR #99) and its last conversation, so the Reg F 7-in-7 counter and the 7-day post-contact cooldown start from the client's real history instead of zero. See Reg F migration seeds. - Server-side mapping templates for the CSV mapper — shipped in core
v0.86.0(published asv0.86.0-prod-0.1; core#1461):/v1/import-mappings, per-tenant and append-only. Editing the mapping or options appends a new version and never rewrites the one an import used; templates are archived, never deleted; and an import that names a template with?mapping_id=(onPOST /v1/debts/importorPOST /v1/debt-lists/{id}/import) records the version it used (GET /v1/import-mappings/{id}/uses). The dashboard reads and saves templates from the server since dashboard v0.41.0 (dashboard#513) and offers to move the ones kept in the browser once. See Import mapping templates. - Per-key request quotas on
/v1— shipped in corev0.86.0(published asv0.86.0-prod-0.1) (core#1471, ADR #193), and in the API Reference. Eachdd_key has its own per-minute budget, every counted response carriesX-RateLimit-*, and a429 rate_limitedcarriesRetry-After; the fixed-window brakes (sign-in, widget tokens, call control, dispositions, invitations) gainRetry-Aftertoo. The dial/SMS rate brakes and the monthly spend caps (429 throttled/429 sms_spend_cap_exceeded) deliberately do not: the platform does not know when their capacity returns. See Rate limits & caps.
Legal coverage matrix and monitoring notice text
Not served. The jurisdiction-by-jurisdiction legal matrix behind recording retention, the price of retention above the floor, and the legal text of the screen-monitoring notice wait on a decision tracked in macro-tasks#415. What core serves today is the contract only: the floors and sources returned by GET /v1/business-types, which cite US federal rules and not state law, and the notice and acknowledgement routes described in Recording transcripts, redaction and the screen notice. Do not read either as a statement of what the law requires.
Channels
Voice, SMS, and email are live in core. Email shipped in core v0.97.0 with provenance-bound addresses, append-only suppression, approved templates, disposition-triggered sends, email_blast, signed provider events, and one-click unsubscribe; it remains off by default per tenant. See the email channel guide. SMS has gated one-off sends, a unified ledger, per-region provider routing, and signed provider webhooks — see the SMS reference and Webhooks. WhatsApp is not built.