TryVox

Consent Ledger

Record opt-ins, opt-outs, and check the current consent status of a phone number.

Consent Ledger

The consent ledger is an append-only record of opt-in and opt-out events keyed by phone_number and consent_type. The Messaging API checks the latest event for a recipient before sending (when consent gating is on).

Record opt-in

POST /v1/compliance/consent

Request body

FieldTypeRequiredDescription
phone_numberstringyesRecipient phone number, E.164 format.
consent_typestringyesChannel/category: sms, whatsapp, voice, marketing, transactional, etc. Free-form.
sourcestringnoWhere the consent came from (web-form, import-2026-01, api-confirmed, etc.). Stored verbatim for audit.

Example

curl -X POST https://api.tryvox.io/v1/compliance/consent \
  -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+919876543210",
    "consent_type": "sms",
    "source": "signup-form-v2"
  }'

Response

201 Created:

{
  "phone_number": "+919876543210",
  "consent_type": "sms",
  "status": "opted_in",
  "consented_at": "2026-04-09T10:30:00Z"
}

Record opt-out

DELETE /v1/compliance/consent

DELETE with a body — the request shape mirrors opt-in. The ledger appends an opted_out event; the prior opt-in stays in history.

Request body

FieldTypeRequiredDescription
phone_numberstringyesRecipient phone number, E.164.
consent_typestringyesSame consent_type you used for the opt-in.
sourcestringnoSource of the opt-out (stop-keyword, support-ticket-1842, etc.).

Example

curl -X DELETE https://api.tryvox.io/v1/compliance/consent \
  -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+919876543210",
    "consent_type": "sms",
    "source": "stop-keyword"
  }'

Response

200 OK:

{
  "phone_number": "+919876543210",
  "consent_type": "sms",
  "status": "opted_out",
  "consented_at": "2026-04-09T10:30:00Z",
  "revoked_at": "2026-04-09T11:14:32Z"
}

Check

Query the current effective consent for a single phone number / type pair.

GET /v1/compliance/consent?phone_number={e164}&consent_type={type}

Example

curl -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
  "https://api.tryvox.io/v1/compliance/consent?phone_number=%2B919876543210&consent_type=sms"

(URL-encode + as %2B in query parameters.)

Response

200 OK if a record exists:

{
  "phone_number": "+919876543210",
  "consent_type": "sms",
  "status": "opted_in",
  "consented_at": "2026-04-09T10:30:00Z"
}

404 NOT_FOUND if no opt-in or opt-out has ever been recorded for this phone_number + consent_type pair. Treat "no record" as "no consent" — the Messaging API does.

Bulk check

Resolve consent for many numbers in a single call.

GET /v1/compliance/consent/bulk?phone_numbers={csv}&consent_type={type}

Query parameters

ParameterDescription
phone_numbersComma-separated E.164 list. URL-encoded. Up to 200 numbers per call.
consent_typeSingle consent_type for the whole batch.

Example

NUMBERS=$(printf '%%2B919876543210,%%2B919876543211,%%2B919876543212')
curl -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
  "https://api.tryvox.io/v1/compliance/consent/bulk?phone_numbers=$NUMBERS&consent_type=sms"

Response

200 OK:

{
  "data": [
    {"phone_number": "+919876543210", "status": "opted_in", "consented_at": "2026-04-09T10:30:00Z"},
    {"phone_number": "+919876543211", "status": "opted_out", "revoked_at": "2026-04-09T11:00:00Z"},
    {"phone_number": "+919876543212", "status": "no_record"}
  ]
}

Numbers with no ledger record are returned with status: "no_record" — they are not omitted. Use this to drive batch sends without a per-number lookup loop.

Errors

StatusCodeReason
400VALIDATION_FAILEDMissing phone_number or consent_type; phone not E.164
401INVALID_CREDENTIALSAuth ID / Auth Token bad
404NOT_FOUND(single Check only) no record for this pair
413BATCH_TOO_LARGEBulk check requested more than 200 numbers

Operational notes

  • The ledger is global per tenant. It is not partitioned by API key Auth ID, so rotating keys does not orphan consent records.
  • Records are durable. There is no record-level delete; opt-outs append, they don't erase. Compliance audits expect this trail.
  • Consent gating is per-channel. Whether a particular send is blocked on missing consent depends on the channel's settings — manage the gate in the dashboard, not via this API.

On this page