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:
| Market | What you need | Notes |
|---|---|---|
| United States | Your own FTC SAN (Subscription Account Number) | Access is priced per area code and renews annually. The first five area codes are free. |
| India | DLT registration as a principal entity, plus the correct number series | Registration on any operator's DLT platform mirrors to the others. |
| Either | Prior express consent, recorded before you dial | Use 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
Request Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
phone_number | string | Yes | — | Phone number to blocklist. Normalized to E.164 on write, so +1 (555) 000-9999 is stored as +15550009999 |
reason | string | No | "API request" | Human-readable reason for blocklisting |
Example Request
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"
}'const result = await rymi.dnc.add({
phone_number: "+15550009999",
reason: "Customer opted out via email",
});result = rymi.dnc.add(
phone_number="+15550009999",
reason="Customer opted out via email",
)Response 201
{
"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
| Status | Meaning |
|---|---|
400 | phone_number is missing or not a valid E.164 number |
401 | Missing or invalid API key |
List DNC Entries
Retrieve a paginated list of blocklisted phone numbers.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Max records to return (max 500) |
offset | integer | 0 | Records to skip |
Example Request
curl "https://api.rymi.live/v1/dnc?limit=20&offset=0" \
-H "Authorization: Bearer YOUR_API_KEY"const { dnc_entries } = await rymi.dnc.list({ limit: 20, offset: 0 });result = rymi.dnc.list(limit=20, offset=0)Response 200
{
"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
| Field | Type | Description |
|---|---|---|
phone | string | Blocklisted phone number in E.164 format |
reason | string | Reason for blocklisting |
created_at | string | ISO 8601 timestamp when the entry was added |
Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
Remove from DNC
Path Parameters
| Parameter | Type | Description |
|---|---|---|
phone | string | Phone number to remove from the DNC list (E.164 format) |
Example Request
curl -X DELETE https://api.rymi.live/v1/dnc/+15550009999 \
-H "Authorization: Bearer YOUR_API_KEY"const result = await rymi.dnc.remove("+15550009999");result = rymi.dnc.remove("+15550009999")Response 200
{
"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
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
Bulk Import
Add up to 1,000 phone numbers to the DNC list in a single request.
Request Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
phone_numbers | string[] | Yes | — | Array of phone numbers in E.164 format (max 1,000) |
reason | string | No | "Bulk API import" | Shared reason applied to all entries |
Example Request
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"
}'const result = await rymi.dnc.addBatch({
phone_numbers: ["+15550009999", "+15550008888", "+15550007777"],
reason: "Customer opt-out list from CRM export",
});result = rymi.dnc.add_batch(
phone_numbers=["+15550009999", "+15550008888", "+15550007777"],
reason="Customer opt-out list from CRM export",
)Response 201
{
"status": "blocklisted",
"count": 3,
"invalid_count": 0,
"invalid": []
}| Field | Type | Description |
|---|---|---|
count | integer | Numbers blocklisted after normalization and de-duplication |
invalid_count | integer | Inputs that failed E.164 validation and were skipped |
invalid | string[] | 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
| Status | Meaning |
|---|---|
400 | Empty phone_numbers array, more than 1,000 entries, or no valid E.164 numbers in the batch |
401 | Missing or invalid API key |
Check DNC Status
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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
phone_numbers | string[] | Yes | — | Array of phone numbers to check (max 500) |
Example Request
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"]
}'const result = await rymi.dnc.check({
phone_numbers: ["+15550009999", "+15551234567"],
});result = rymi.dnc.check(
phone_numbers=["+15550009999", "+15551234567"],
)Response 200
{
"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
| Field | Type | Description |
|---|---|---|
results | array | Per-number check results, in input order |
results[].phone_number | string | The phone number exactly as you sent it |
results[].normalized | string | null | E.164 form used for the lookup; null if the input couldn't be parsed |
results[].blocked | boolean | true if the number is on the DNC list |
results[].valid | boolean | false if the input is not a valid phone number (blocked is always false in that case) |
blocked_count | integer | Total numbers found on the DNC list |
total_checked | integer | Total numbers checked |
Errors
| Status | Meaning |
|---|---|
400 | Empty phone_numbers array or exceeds 500 limit |
401 | Missing 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.
Record 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
| Parameter | Type | Description |
|---|---|---|
id | uuid | Contact ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | voice, sms, whatsapp, or telegram |
granted | boolean | Yes | true to grant, false to revoke |
source | string | No | Where consent came from, e.g. web_form, import, call, api (default api) |
method | string | No | How it was obtained, e.g. checkbox, verbal, double_opt_in (default api) |
Example Request
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"
}'const result = await rymi.contacts.setConsent("9c1e...contact", {
channel: "voice",
granted: true,
source: "web_form",
method: "checkbox",
});result = rymi.contacts.set_consent(
"9c1e...contact",
channel="voice",
granted=True,
source="web_form",
method="checkbox",
)Response 200
{
"contact_id": "9c1e...contact",
"channel": "voice",
"consent": { "granted": true, "at": "2026-07-11T10:00:00Z", "source": "web_form", "method": "checkbox" }
}Errors
| Status | Meaning |
|---|---|
400 | channel is not one of the supported values, or granted is missing |
401 | Missing or invalid API key |
404 | Contact not found |
Record an Attestation
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
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | dnc_external_scrub or ai_disclosure |
note | string | No | Free-text note, e.g. which vendor/list was used and when (max 500 characters) |
Example Request
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"
}'const result = await rymi.compliance.attest({
kind: "dnc_external_scrub",
note: "Scrubbed against vendor X National DNC export on 2026-07-11",
});result = rymi.compliance.attest(
kind="dnc_external_scrub",
note="Scrubbed against vendor X National DNC export on 2026-07-11",
)Response 201
{
"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
| Status | Meaning |
|---|---|
400 | kind is missing or not one of the supported values |
401 | Missing or invalid API key |
403 | Publishable keys cannot record attestations |
List Attestations
Returns the tenant's 50 most recent compliance attestations, newest first.
Example Request
curl https://api.rymi.live/v1/compliance/attestations \
-H "Authorization: Bearer YOUR_API_KEY"const { attestations } = await rymi.compliance.listAttestations();result = rymi.compliance.list_attestations()Response 200
{
"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
| Status | Meaning |
|---|---|
401 | Missing 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.

