Skip to content

Compliance

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.

Suppression data the tenant owns: its internal do-not-call list, uploaded as CSV into the same list the pre-dial gate reads.

Endpoints

MethodPathSummary
POST/v1/suppressions/importUpload the tenant's own do-not-call list (CSV)
GET/v1/artificial-voice-disclosureFetch the tenant NON-AI artificial-voice disclosure
PUT/v1/artificial-voice-disclosureStore the tenant NON-AI artificial-voice disclosure

Upload the tenant's own do-not-call list (CSV)

http
POST /v1/suppressions/import

Loads the tenant's INTERNAL suppression list from a text/csv body.

Rows join the internal_tenant DNC list that the PRE-DIAL compliance gate already consults on every call and every SMS — the same list SMS STOP writes. There is no second suppression path and nothing to enable: an accepted number is suppressed for the next attempt.

Format: one row per number, number[,reason]. A header row (number, phone, phone_number, telephone, e164, msisdn) is optional; blank lines and lines starting with # are skipped. Numbers are normalised the same way the regulatory dumps are: E.164 is kept, a bare 10-digit or 1+10-digit NANP number becomes +1XXXXXXXXXX.

Malformed rows are REPORTED, not fatal: refusing a whole file over one typo would leave the remaining consumers dialable, which is the expensive direction of the error. Rejected values come back MASKED — the line number is what identifies the row to fix.

Re-uploading a list is idempotent (recorded counts rows actually written, so an overlap reads as an overlap and not as new suppressions).

Limits: 1 000 000 bytes of body, 10 000 data rows, 200 characters of reason, and at most 100 entries in rejected (rejected_count always carries the true total).

Responses

200

Upload outcome. A 200 does NOT mean every row was accepted — rejected_count is authoritative.

FieldTypeRequiredDescription
receivedintegeryesData rows the document offered (header/blank lines excluded).
acceptedintegeryesRows that normalised to a distinct E.164 number.
recordedintegeryesRows actually written. Lower than accepted when the list overlaps suppressions already on file.
rejected_countintegeryes
rejectedarray of objectyesUp to 100 entries; see rejected_count for the total.
rejected[].lineintegeryes1-based line number in the uploaded document.
rejected[].valuestringyesThe offending value, masked to its last 4 characters.
rejected[].reasonnot_e164 · duplicateyes
json
{
  "received": 3,
  "accepted": 2,
  "recorded": 2,
  "rejected_count": 1,
  "rejected": [
    {
      "line": 4,
      "value": "***5551",
      "reason": "not_e164"
    }
  ]
}

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

413

The CSV body exceeds 1 000 000 bytes.

json
{
  "error": {
    "code": "payload_too_large",
    "message": "csv body exceeds 1000000 bytes"
  }
}

415

The request body is not text/csv.

json
{
  "error": {
    "code": "unsupported_media_type",
    "message": "this endpoint takes a text/csv body"
  }
}

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

Fetch the tenant NON-AI artificial-voice disclosure

http
GET /v1/artificial-voice-disclosure

The stored disclosure for the NON-AI artificial-voice lane (TTS / IVR / survey). A tenant with nothing stored is 404 not_found — NOT a 200 with a null body: "no disclosure stored" is a real compliance state, and a client branching on the status code must not read it as configured.

Responses

200

The stored disclosure.

FieldTypeRequiredDescription
artificial_voice_disclosureobject (ArtificialVoiceDisclosure)yes
artificial_voice_disclosure.textstringyesThe stored disclosure text.
artificial_voice_disclosure.sourceapi · import · migrationyesProvenance — how the row got here. Server-set, never taken from the body.
artificial_voice_disclosure.actorstringyesThe principal that wrote it, narrowest-first: the acting agent id, else the dashboard user id when the caller is a ddu_ session, else tenant-api-key for a dd_ key that carries no finer identity. Server-set.
artificial_voice_disclosure.validbooleanyesWhether the stored text STILL passes the content check. Re-computed on every read, not stored: the marker vocabulary can gain a term, and a row written under the older one must not keep vouching for a text the gate would no longer accept. This is the same question the dialing path asks.
artificial_voice_disclosure.updated_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"
  }
}

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

Store the tenant NON-AI artificial-voice disclosure

http
PUT /v1/artificial-voice-disclosure

Upsert on the tenant (one row, last write wins). The TEXT is judged by the SAME content check the AI lane applies: it must clearly identify the voice as automated, in English or Spanish. A text that does not is 400 bad_request and NOTHING is stored — a gate cannot be handed a true the text has not earned.

source and actor are provenance and are set by the SERVER (api plus the acting principal); sending either is 400 bad_request naming the key. Tenant-management scope (dd_ API key or dashboard login; ddw_403).

Request body (JSON, required)

FieldTypeRequiredDescription
textstringyesMust clearly identify the voice as automated (EN or ES). Anything else is 400 bad_request and nothing is stored. The ceiling exists because the dialing path re-reads this row on every attempt. The EFFECTIVE ceiling is 10000 BYTES (UTF-8), enforced by the server and by a database CHECK. JSON Schema maxLength counts CHARACTERS and has no byte-length keyword, so the maxLength: 10000 above is an UPPER bound, not the real limit: a multibyte text can be refused with 400 bad_request well before reaching 10000 characters. The machine-readable form of the real limit is x-maxLengthBytes. See ADR #74.
json
{
  "text": "Hello, this is an automated call from Acme Collections."
}

Responses

200

The stored disclosure.

FieldTypeRequiredDescription
artificial_voice_disclosureobject (ArtificialVoiceDisclosure)yes
artificial_voice_disclosure.textstringyesThe stored disclosure text.
artificial_voice_disclosure.sourceapi · import · migrationyesProvenance — how the row got here. Server-set, never taken from the body.
artificial_voice_disclosure.actorstringyesThe principal that wrote it, narrowest-first: the acting agent id, else the dashboard user id when the caller is a ddu_ session, else tenant-api-key for a dd_ key that carries no finer identity. Server-set.
artificial_voice_disclosure.validbooleanyesWhether the stored text STILL passes the content check. Re-computed on every read, not stored: the marker vocabulary can gain a term, and a row written under the older one must not keep vouching for a text the gate would no longer accept. This is the same question the dialing path asks.
artificial_voice_disclosure.updated_atstring (date-time)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.