Consent and DNC
RelationsAI enforces calling rules on every call, whether it is placed from the console or the API. This guide explains the do-not-call list, consent, calling hours and AI disclosure, how to manage DNC entries through the API and what a refused call looks like.
These rules exist to protect the people you call, and breaking them can carry legal penalties for your business. No API key, scope or request option can switch them off, and the API refuses to start or resume a campaign whose do-not-call screening a person switched off in the console. For campaigns the timing matters: see When the list takes effect.
Before you begin
dnc:read: Listing and looking up do-not-call entriesdnc:write: Adding and removing entriescontacts:write: Recording consent on contacts
The workspace's compliance settings (whether consent is required, the calling window, the per-number daily limit) are set by an admin in the console. No API key can read or change them; that is deliberate.
Rules that always apply
Every single call (placed with POST/calls or from the console) passes these checks before anything is dialled, and every campaign applies them to all of its recipients when it starts and again when it resumes. Campaigns are not re-checked before each call: see When the list takes effect. The order of the checks is:
- Do-not-call list: calls to the number are refused, and campaigns leave it out when they start or resume (always, in both modes). Refusal:
dnc_listed. - Consent: when the workspace requires it, a contact without recorded consent is not called (when an admin switched it on). Refusal:
consent_required. - Calling hours: calls only inside the workspace's calling window (when an admin set a window). Refusal:
outside_calling_hours. - Per-number daily limit: at most N calls to one number in a rolling 24 hours (when an admin set a limit). Refusal:
contact_daily_limit_reached.
You see the first rule that blocks the call. A refused call is not placed and not charged.
AI disclosure: an agent cannot be published unless its instructions tell the people it calls that they are speaking with an AI. This is checked when the agent is published; RelationsAI does not verify what is said on each call, so keep the disclosure at the start of your agent's instructions and check it in transcripts.
Test mode enforces the same rules with test-mode data: a test-mode do-not-call entry or a test-mode contact without consent refuses a test call exactly as in live mode. Test-mode entries never affect live calls, and live entries never affect test calls.
Managing the do-not-call list
Add a number as soon as someone asks not to be called, from whichever channel they asked in (a call, an email, your website, an agent's note).
POST/dnc-entries adds a number. It is safe to repeat: adding a number that is already listed returns the existing entry with 200 instead of 201.
Complete runnable example
Add a number to the do-not-call list. Safe to repeat.
The same steps as a complete script, with error handling, tested against test mode on every docs build. Each script reads your test key from an environment variable named in its first lines.
- cURL script (bash)
add-dnc-entry.sh - JavaScript (Node.js 20+)
add-dnc-entry.mjs - Python 3.9+ (requests)
add-dnc-entry.py
The entry (201):
JSON
What adding a number does, immediately and in the key's mode:
- Calls to it are refused with
dnc_listed. - Campaigns leave it out at start, and a paused campaign that still holds it cannot be resumed (see Running campaigns).
- A campaign that is already running is not screened again: see below.
- If a contact has this number, the contact is marked
do_not_call: trueand its consent is withdrawn. If no contact has it, a contact holding only the number is created, so the number is visible in the console. created_atis recorded in test mode; in live mode it isnulltoday because the time is not stored yet.
When the list takes effect
The list is checked at three moments, in both modes:
| Moment | What happens to a listed number |
|---|---|
Every POST/calls | Refused with dnc_listed |
| Every campaign start | Left out of the run (suppressed) |
| Every campaign resume | The resume is refused (campaign_recipients_blocked) while it is a remaining recipient |
A campaign that is already running is not checked again before each call. If you add a number while a campaign is running and that person has not been called yet, the run still calls them. To apply the new entry at once, pause the campaign and resume it (the resume is refused while the number is a remaining recipient; then follow the remedy in Running campaigns), or stop it. Test mode behaves the same way, so you can try this safely.
Matching ignores formatting: a number matches by its digits, and by its last ten digits when both numbers have at least ten. So +919000000003 also blocks a contact stored as 9000000003.
To check one number, list entries filtered by it:
A listed number comes back as one entry in data; an empty data means the number is not on the list.
Complete runnable example
Is this number on the do-not-call list?
The same steps as a complete script, with error handling, tested against test mode on every docs build. Each script reads your test key from an environment variable named in its first lines.
- cURL script (bash)
check-number.sh - JavaScript (Node.js 20+)
check-number.mjs - Python 3.9+ (requests)
check-number.py
You do not need to check before calling: the API checks every call. Check when you want to show the status in your own system.
To mirror your list into RelationsAI, page through GET/dnc-entries (a stable order; live entries have no time added yet) and add what is missing on your side, and add every number your system marks as do-not-call. Keep the two lists in sync in both directions.
Removing an entry
DEL/dnc-entries/{dnc_entry_id} removes a number from the list, so it can be called again. Removal should be rare and deliberate: do it only when the entry was added by mistake, or the person has clearly asked to be called again. Keep a record on your side of who asked and when.
Response 200:
JSON
Complete runnable example
Remove a do-not-call entry. Only do this when the entry was added by mistake.
The same steps as a complete script, with error handling, tested against test mode on every docs build. Each script reads your test key from an environment variable named in its first lines.
- cURL script (bash)
remove-dnc-entry.sh - JavaScript (Node.js 20+)
remove-dnc-entry.mjs - Python 3.9+ (requests)
remove-dnc-entry.py
- The removal is recorded in the workspace audit log with the API key that made it.
- Consent is not restored. If the workspace requires consent, record it again on the contact only if the person gives it.
- A contact whose number is on the list cannot be deleted (
contact_on_dnc_list), so deleting a contact can never quietly remove a protection.
Consent
When an admin switches on the consent requirement, a call to a number that matches a contact whose consent_granted is false is refused with consent_required. A number that matches no contact is not blocked by this rule: there is no consent record to contradict.
Record consent through the contacts API, from your own records of when and how the person agreed:
JSON
sent to POST/contacts/upsert (or PATCH on the contact). true is recorded with the time and the API as its source; false records a withdrawal. Only set true when the person really consented. Setting consent to make a refusal go away is exactly what the rule exists to prevent.
Consent is withdrawn automatically when a number is added to the do-not-call list. The do-not-call list always wins: while a number is listed, calls to it are refused whatever consent_granted says, and recording consent for it does not make it callable. Removing the entry later does not restore consent either.
Calling hours and the daily limit
- The calling window is evaluated in the timezone of the workspace's calling region, which is not necessarily the
timezoneshown on GET/workspace, and never the contact's. A refusal'sdetailsgives the window and the timezone it was evaluated in, for example{"window": "09:00-21:00", "timezone": "Asia/Kolkata"}. If the window is switched on with no hours set, every call is refused (details.windowisnull). - Campaigns: a start or resume outside the window is refused as a whole.
- The per-number daily limit counts calls to one number over the last 24 hours, from the console, the API and campaigns. It is separate from the workspace's daily limit of calls placed through the API (
daily_call_limit_reached).
Refusals
A refusal is a normal API error with retry: "no":
JSON
What each refusal means and who can change it:
dnc_listed: The person asked not to be called. Do not call. Mark the record as do-not-call in your system. Who can change it: Nobody, unless the entry was a mistake.consent_required: Consent is required and this contact has none. Get consent through your own process first. Who can change it: Your team, with the person.outside_calling_hours: Outside the calling window. Retry inside the window indetails. Who can change it: An admin can change the window in the console.contact_daily_limit_reached: Called too often in 24 hours. Try again after the window. Who can change it: An admin can review the limit.campaign_recipients_suppressed: Every campaign recipient is blocked. Choose callable recipients. Who can change it: Your team.campaign_recipients_blocked: A resume found newly blocked recipients. Create a new campaign with the still-pending recipients minus the blocked ones, then stop this one (never stop and start it: that calls everyone again). Who can change it: Your team.campaign_dnc_override_enabled: Do-not-call screening was switched off for this campaign in the console; the API will not start or resume it. Who can change it: A workspace admin, in the console.
What can go wrong
- A number you added is still called by a running campaign: Campaigns screen at start and at resume; calls already connected finish. Fix: Pause or stop the campaign; a resume is refused while the number is in its run.
consent_requiredfor someone who agreed: Consent recorded in your system but not in RelationsAI. Fix: Upsert the contact withconsent_granted: truefrom your record.- Consent was set but calls are still refused after a removal from the list: Removing an entry does not restore consent. Fix: Record consent again only if the person gives it.
contact_limit_reachedon add: Adding an unknown number creates a contact. Fix: Delete contacts you no longer need, or contact support.insufficient_scope: The key lacksdnc:write. Fix: Ask an admin for a key with it.