Skip to content

Compliance

Manage your Do-Not-Call (DNC) registry, record consent for a contact, and file compliance attestations used by campaign launch blockers. These are mechanisms, not legal certification — see the Campaigns guide's Compliance section for the full picture and its limits.

Who is responsible for what

Read this before running outbound campaigns. It determines what you have to put in place yourself.

Rymi checks your own suppression list. Every number you add through the endpoints below is checked before an outbound call is queued, on single calls and campaigns alike. A suppressed number is rejected at request validation and never reaches your agent.

Rymi does not scrub national registries. Numbers on the US National Do Not Call Registry, US state registries, or India's DND registry are not filtered by Rymi. If you dial them, you dial them.

This is not a gap we intend to close centrally, because we cannot. Under the FTC's Telemarketing Sales Rule, a service provider may not use Registry data to place calls on behalf of more than one seller unless each seller holds its own subscription. You are the seller. One Rymi-wide subscription scrubbing on your behalf would itself violate the rule. The same logic applies in India, where DLT registration and the 140/1600 number series are obligations of the principal entity — you — not the platform.

What that means in practice:

MarketWhat you needNotes
United StatesYour own FTC SAN (Subscription Account Number)Access is priced per area code and renews annually. The first five area codes are free.
IndiaDLT registration as a principal entity, plus the correct number seriesRegistration on any operator's DLT platform mirrors to the others.
EitherPrior express consent, recorded before you dialUse consent records to keep the evidence.

Most teams satisfy this by scrubbing their list before import, or by subscribing to a scrubbing vendor. Once a number is suppressed, push it to your Rymi DNC list — in bulk, up to 1,000 at a time — so Rymi enforces it on every subsequent call.

This mirrors how the rest of the category works: Twilio, Vapi, Bland, and Retell all place national-registry scrubbing on the customer. It is not legal advice — confirm your obligations with counsel for the markets you dial.

Add to DNC List

POST/v1/dnc

Request Body

FieldTypeRequiredDefaultDescription
phone_numberstringYesPhone number to blocklist. Normalized to E.164 on write, so +1 (555) 000-9999 is stored as +15550009999
reasonstringNo"API request"Human-readable reason for blocklisting

Example Request

bash
curl -X POST https://api.rymi.live/v1/dnc \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+15550009999",
    "reason": "Customer opted out via email"
  }'
ts
const result = await rymi.dnc.add({
  phone_number: "+15550009999",
  reason: "Customer opted out via email",
});
python
result = rymi.dnc.add(
    phone_number="+15550009999",
    reason="Customer opted out via email",
)

Response 201

json
{
  "status": "blocklisted",
  "phone_number": "+15550009999"
}

Adding a number that is already blocklisted succeeds. The entry is upserted, so the operation is idempotent and retries are safe.

Errors

StatusMeaning
400phone_number is missing or not a valid E.164 number
401Missing or invalid API key

List DNC Entries

GET/v1/dnc

Retrieve a paginated list of blocklisted phone numbers.

Query Parameters

ParameterTypeDefaultDescription
limitinteger50Max records to return (max 500)
offsetinteger0Records to skip

Example Request

bash
curl "https://api.rymi.live/v1/dnc?limit=20&offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY"
ts
const { dnc_entries } = await rymi.dnc.list({ limit: 20, offset: 0 });
python
result = rymi.dnc.list(limit=20, offset=0)

Response 200

json
{
  "dnc_entries": [
    {
      "phone": "+15550009999",
      "reason": "Customer opted out via email",
      "created_at": "2026-03-01T10:00:00Z"
    }
  ],
  "total": 1,
  "offset": 0,
  "limit": 20
}

Response Fields

FieldTypeDescription
phonestringBlocklisted phone number in E.164 format
reasonstringReason for blocklisting
created_atstringISO 8601 timestamp when the entry was added

Errors

StatusMeaning
401Missing or invalid API key

Remove from DNC

DELETE/v1/dnc/:phone

Path Parameters

ParameterTypeDescription
phonestringPhone number to remove from the DNC list (E.164 format)

Example Request

bash
curl -X DELETE https://api.rymi.live/v1/dnc/+15550009999 \
  -H "Authorization: Bearer YOUR_API_KEY"
ts
const result = await rymi.dnc.remove("+15550009999");
python
result = rymi.dnc.remove("+15550009999")

Response 200

json
{
  "status": "removed",
  "phone": "+15550009999"
}

Removal is idempotent. Deleting a number that isn't on the list still returns 200. The input is normalized to E.164 before matching, so format differences don't prevent removal.

Errors

StatusMeaning
401Missing or invalid API key

Bulk Import

POST/v1/dnc/batch

Add up to 1,000 phone numbers to the DNC list in a single request.

Request Body

FieldTypeRequiredDefaultDescription
phone_numbersstring[]YesArray of phone numbers in E.164 format (max 1,000)
reasonstringNo"Bulk API import"Shared reason applied to all entries

Example Request

bash
curl -X POST https://api.rymi.live/v1/dnc/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_numbers": ["+15550009999", "+15550008888", "+15550007777"],
    "reason": "Customer opt-out list from CRM export"
  }'
ts
const result = await rymi.dnc.addBatch({
  phone_numbers: ["+15550009999", "+15550008888", "+15550007777"],
  reason: "Customer opt-out list from CRM export",
});
python
result = rymi.dnc.add_batch(
    phone_numbers=["+15550009999", "+15550008888", "+15550007777"],
    reason="Customer opt-out list from CRM export",
)

Response 201

json
{
  "status": "blocklisted",
  "count": 3,
  "invalid_count": 0,
  "invalid": []
}
FieldTypeDescription
countintegerNumbers blocklisted after normalization and de-duplication
invalid_countintegerInputs that failed E.164 validation and were skipped
invalidstring[]The skipped inputs, verbatim, so you can correct and resubmit them

Numbers that fail validation are skipped rather than fatal, so the batch never fails as a whole. Duplicates (already on the DNC list, or the same number in two formats) are upserted once, so the operation is idempotent.

Errors

StatusMeaning
400Empty phone_numbers array, more than 1,000 entries, or no valid E.164 numbers in the batch
401Missing or invalid API key

Check DNC Status

POST/v1/dnc/check

Check whether one or more phone numbers are on the DNC list without adding them. Useful for pre-flight validation before queuing outbound calls.

Request Body

FieldTypeRequiredDefaultDescription
phone_numbersstring[]YesArray of phone numbers to check (max 500)

Example Request

bash
curl -X POST https://api.rymi.live/v1/dnc/check \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_numbers": ["+15550009999", "+15551234567"]
  }'
ts
const result = await rymi.dnc.check({
  phone_numbers: ["+15550009999", "+15551234567"],
});
python
result = rymi.dnc.check(
    phone_numbers=["+15550009999", "+15551234567"],
)

Response 200

json
{
  "results": [
    { "phone_number": "+15550009999", "normalized": "+15550009999", "blocked": true, "valid": true },
    { "phone_number": "+1 (555) 123-4567", "normalized": "+15551234567", "blocked": false, "valid": true }
  ],
  "blocked_count": 1,
  "total_checked": 2
}

Response Fields

FieldTypeDescription
resultsarrayPer-number check results, in input order
results[].phone_numberstringThe phone number exactly as you sent it
results[].normalizedstring | nullE.164 form used for the lookup; null if the input couldn't be parsed
results[].blockedbooleantrue if the number is on the DNC list
results[].validbooleanfalse if the input is not a valid phone number (blocked is always false in that case)
blocked_countintegerTotal numbers found on the DNC list
total_checkedintegerTotal numbers checked

Errors

StatusMeaning
400Empty phone_numbers array or exceeds 500 limit
401Missing or invalid API key

How DNC Filtering Works

When you call POST /v1/calls or POST /v1/calls/batch with PSTN participants, Rymi cross-references the requested numbers against the DNC registry for the authenticated tenant. Both sides are compared in normalized E.164 form, so format differences can't slip past the check. Matching numbers are rejected before queueing with a 400 error stating that one or more (or all) requested numbers are on your Do-Not-Call list.

Bulk Operations

Use POST /v1/dnc/batch to blocklist up to 1,000 numbers at once, and POST /v1/dnc/check to verify numbers before queuing calls.


PATCH/v1/contacts/:id/consent

Records a grant or revoke of a contact's consent for one channel, with evidence (when/how it was obtained), and appends an immutable row to the consent_events proof log. The contact's consent field holds the current state; consent_events holds the full history.

Path Parameters

ParameterTypeDescription
iduuidContact ID

Request Body

FieldTypeRequiredDescription
channelstringYesvoice, sms, whatsapp, or telegram
grantedbooleanYestrue to grant, false to revoke
sourcestringNoWhere consent came from, e.g. web_form, import, call, api (default api)
methodstringNoHow it was obtained, e.g. checkbox, verbal, double_opt_in (default api)

Example Request

bash
curl -X PATCH https://api.rymi.live/v1/contacts/9c1e.../consent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "voice",
    "granted": true,
    "source": "web_form",
    "method": "checkbox"
  }'
ts
const result = await rymi.contacts.setConsent("9c1e...contact", {
  channel: "voice",
  granted: true,
  source: "web_form",
  method: "checkbox",
});
python
result = rymi.contacts.set_consent(
    "9c1e...contact",
    channel="voice",
    granted=True,
    source="web_form",
    method="checkbox",
)

Response 200

json
{
  "contact_id": "9c1e...contact",
  "channel": "voice",
  "consent": { "granted": true, "at": "2026-07-11T10:00:00Z", "source": "web_form", "method": "checkbox" }
}

Errors

StatusMeaning
400channel is not one of the supported values, or granted is missing
401Missing or invalid API key
404Contact not found

Record an Attestation

POST/v1/compliance/attestations

Records a compliance attestation for the tenant — an append-only note, not a setting. A campaign's compliance_policy.dnc_scrub: "attested" or ai_disclosure: "attested" requires a current attestation of the matching kind (current = recorded within the last 31 days) before it can launch.

Request Body

FieldTypeRequiredDescription
kindstringYesdnc_external_scrub or ai_disclosure
notestringNoFree-text note, e.g. which vendor/list was used and when (max 500 characters)

Example Request

bash
curl -X POST https://api.rymi.live/v1/compliance/attestations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "dnc_external_scrub",
    "note": "Scrubbed against vendor X National DNC export on 2026-07-11"
  }'
ts
const result = await rymi.compliance.attest({
  kind: "dnc_external_scrub",
  note: "Scrubbed against vendor X National DNC export on 2026-07-11",
});
python
result = rymi.compliance.attest(
    kind="dnc_external_scrub",
    note="Scrubbed against vendor X National DNC export on 2026-07-11",
)

Response 201

json
{
  "attestation": {
    "id": "b1f0...attestation",
    "tenant_id": "t_...",
    "kind": "dnc_external_scrub",
    "attested_by": "user_...",
    "note": "Scrubbed against vendor X National DNC export on 2026-07-11",
    "created_at": "2026-07-11T10:00:00Z"
  }
}

Errors

StatusMeaning
400kind is missing or not one of the supported values
401Missing or invalid API key
403Publishable keys cannot record attestations

List Attestations

GET/v1/compliance/attestations

Returns the tenant's 50 most recent compliance attestations, newest first.

Example Request

bash
curl https://api.rymi.live/v1/compliance/attestations \
  -H "Authorization: Bearer YOUR_API_KEY"
ts
const { attestations } = await rymi.compliance.listAttestations();
python
result = rymi.compliance.list_attestations()

Response 200

json
{
  "attestations": [
    {
      "id": "b1f0...attestation",
      "tenant_id": "t_...",
      "kind": "dnc_external_scrub",
      "attested_by": "user_...",
      "note": "Scrubbed against vendor X National DNC export on 2026-07-11",
      "created_at": "2026-07-11T10:00:00Z"
    }
  ]
}

Errors

StatusMeaning
401Missing or invalid API key

Not legal certification

An attestation is a record that you did something, not a Rymi-issued compliance certificate. Legal sufficiency of your consent capture, DNC scrubbing, and AI disclosure is your responsibility with counsel.