Do-Not-Call (DNC)

Do-Not-Call (DNC) API

The Do‑Not‑Call list is your suppression list: numbers on it are never dialed by campaigns (or one‑off outbound/transfer calls). Use it to honor opt‑outs and stay TCPA/DNC compliant. All endpoints are organization‑scoped under /api/orgs/{orgId}/dnc. See the Do‑Not‑Call product page for the dashboard workflow.

Authentication

These are dashboard (org‑scoped) endpoints, authenticated with a user JWT:

Authorization: Bearer eyJ…
X-Org-Id: {orgId}

See Authentication. Responses are wrapped: { "success": true, "data": { … } } (or { "success": false, "error": "…" }).

Roles: any member can read (GET, export). Mutations — add, import, remove, bulk‑delete — require owner, admin, or developer.

Endpoints

MethodPathPurpose
GET/api/orgs/{orgId}/dncList suppressed numbers (paginated, searchable)
POST/api/orgs/{orgId}/dncAdd one number
POST/api/orgs/{orgId}/dnc/importBulk import via file upload (multipart)
GET/api/orgs/{orgId}/dnc/exportExport the list as CSV
POST/api/orgs/{orgId}/dnc/bulk-deleteRemove many entries by id
DELETE/api/orgs/{orgId}/dnc/{id}Remove one entry

List

Query paramDefaultNotes
limit100Page size.
offset0For paging.
searchCase‑insensitive substring match on the number or reason.
curl "https://api.telenow.ai/api/orgs/{orgId}/dnc?search=opt-out&limit=100" \
  -H "Authorization: Bearer eyJ…" -H "X-Org-Id: {orgId}"
{
  "success": true,
  "data": {
    "entries": [
      {
        "id": "…", "org_id": "…",
        "phone_number": "+14155550123",
        "reason": "customer opt-out",
        "source": "manual",
        "created_by": "…",
        "created_at": "2026-06-13T10:00:00Z",
        "session_id": null,
        "call_content_available": false
      },
      {
        "id": "…", "org_id": "…",
        "phone_number": "+14155550199",
        "reason": "caller asked not to be contacted (call 6f1c…)",
        "source": "opt_out",
        "created_by": null,
        "created_at": "2026-06-13T11:20:00Z",
        "session_id": "6f1c…",
        "call_content_available": true
      }
    ],
    "total": 2
  }
}

source records how the entry was added — manual (single add), csv (import), opt_out (the caller asked, on a call, or you revoked their consent), or client (added by one of your clients, if you resell Telenow). Treat it as an open set.

The call behind an opt‑out

session_id is the call the suppression was asked for on. It is set only when the agent recorded it live — its own opt_out tool, or the keypad opt‑out key — so it is how you tell an entry a machine created from one a person typed, and it is the id you pass to GET /api/orgs/{orgId}/calls/{id} to fetch the recording and transcript that prove it. created_by is null on exactly these entries: there was no acting user, only the caller.

It comes back only when the call resolves inside your organization, so a non‑null value is always fetchable. null on an opt_out entry means the entry is still a recorded opt‑out but has no call to open — a consent revocation you made yourself, or an older entry whose reference was overwritten by a later manual re‑add.

call_content_available says whether that call's transcript and recording are still inside your content‑retention window. The call record outlives its content, so false means the call page will still tell you when it happened and which agent took it, but the audio and transcript are gone. It is computed only by this endpoint — POST and DELETE responses omit it.

Add a number

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/dnc \
  -H "Authorization: Bearer eyJ…" -H "X-Org-Id: {orgId}" \
  -H "Content-Type: application/json" \
  -d '{ "phoneNumber": "+14155550123", "reason": "customer opt-out" }'
  • phoneNumber is required (alias phone_number accepted); reason is optional.
  • Numbers are canonicalized on the way in: a leading + is kept and all other non‑digits are stripped. A value with no digits (e.g. "n/a") is a 400.
  • Adding a number that's already on the list is idempotent — it updates the reason (if you supplied one) rather than erroring, and returns the existing entry.

Bulk import

POST …/dnc/import takes a multipart/form-data upload (field name file, csv, or numbers). The file may be either:

  • a headerless plain‑text list — one number per line, or
  • a CSV whose first column is the number and an optional second column is the reason (phone,reason).

No header detection is needed: a non‑numeric token (such as a literal phone_number header) yields no digits and is simply skipped.

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/dnc/import \
  -H "Authorization: Bearer eyJ…" -H "X-Org-Id: {orgId}" \
  -F "[email protected]"
+14155550123,customer opt-out
+14155550199,complaint
+14155550111

Response counts what landed vs. what was skipped (un‑parseable rows and numbers already on the list both count as skipped):

{ "success": true, "data": { "added": 2, "skipped": 1 } }

Export (CSV)

GET …/dnc/export streams the (optionally searched) suppression list as a CSV download, capped at 500,000 rows. Columns: phone_number, reason, source, added_at, session_id (the last is populated for opt_out entries only). The first two columns are what import reads back, so an export → edit → re-import round trip still works.

If the file is incomplete, the response says so rather than just stopping: X-Export-Truncated: true, plus X-Export-Rows (what you got) and X-Export-Total (what matched), and the filename gains -partial-<rows>-of-<total>. X-Export-Truncated is sent on every export, so false is a positive "this file is whole" rather than silence. The dashboard shows a notice too. See the campaigns export for the full description.

curl "https://api.telenow.ai/api/orgs/{orgId}/dnc/export?search=complaint" \
  -H "Authorization: Bearer eyJ…" -H "X-Org-Id: {orgId}" -o dnc-list.csv

Remove entries

Remove one by id, or many at once:

# single
curl -X DELETE https://api.telenow.ai/api/orgs/{orgId}/dnc/{id} \
  -H "Authorization: Bearer eyJ…" -H "X-Org-Id: {orgId}"

# bulk (max 10,000 ids per request)
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/dnc/bulk-delete \
  -H "Authorization: Bearer eyJ…" -H "X-Org-Id: {orgId}" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["…", "…"] }'

bulk-delete returns { "success": true, "data": { "removed": N, "protected": M } } — ids not in your org are silently ignored. Removing an entry does not resurrect campaign calls that were already skipped while the number was suppressed; re‑upload the target to dial it again.

opt_out entries are refused by bulk-delete and counted in protected instead: they are the record a complaint is answered with, so a re‑uploaded suppression file plus a select‑all cannot wipe them in one pass. Deleting one deliberately, via single DELETE, is still allowed — check the call it names first, and note that the withdrawal is written to your audit log with the number, the call and the original opt‑out date so it can be reconstructed if it turns out to have been a mistake.

When suppression takes effect

Understanding the timing matters for compliance:

  1. At target upload — when you add campaign targets, any number already on the DNC list is dropped immediately and counted as suppressed (it never enters the dial queue).
  2. At dial time — the dialer re‑checks the list before placing each call, so a number you add to the DNC list after uploading still won't be dialed: any still‑pending campaign call to it is flipped to suppressed (skipped) instead.
  3. One‑off calls — operator‑initiated calls, the softphone passthrough, and live‑call transfers also honor the list.

So adding a number to the DNC list reliably stops future calls to it. What it does not do is undo a call already placed, or un‑skip a target after you later remove the number.

Best practices

  • Add a number to the DNC list the moment a contact opts out — e.g. from a tool.invoked webhook, your CRM, or directly in the dashboard.
  • Keep this list authoritative across all your dialing so you never re‑contact an opted‑out party.
  • Export periodically to reconcile with your system of record, and import to seed the list when you onboard.