Open in
Opt-outs
Your organization
The opt-out list holds numbers that asked your organization not to text them, not to call them, or both. ThunderPhone checks it before every outbound text and call:
- Texts to a number opted out of
smsare refused:POST /v1/sms, the agent'ssend_smstool, post-call follow-ups, retries and scheduled texts. - Calls to a number opted out of
callare refused:POST /v1/call, campaigns and scheduled calls. - Adding a number skips the queued scheduled sends
to it on that channel, with status
skippedand reasonopted_out.
Use these endpoints to keep the list in step with your own CRM's opt-outs.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/opt-outs | List opt-outs |
POST | /v1/opt-outs | Add a number |
DELETE | /v1/opt-outs | Remove a number |
Authenticate with an organization API key. These endpoints are not available to OAuth clients. In a dashboard session any member can list and add numbers; removing one requires an admin or owner, because it lets your organization contact someone who asked it not to. Organization API keys are not limited by role.
Opt-out object
{
"id": 318,
"number": "+14155550199",
"channel": "sms",
"source": "api",
"source_reference": "api_key:12",
"blocks": true,
"created_at": "2026-11-01T18:02:11.204Z",
"updated_at": "2026-11-01T18:02:11.204Z"
}| Field | Type | Description |
|---|---|---|
id | integer | Id of the row |
number | string | E.164 |
channel | string | sms or call. A number opted out of both has one row for each |
source | string | Where it came from: api, manual (added in the dashboard) or carrier. inbound_stop (the person replied STOP) is reserved for inbound SMS support and not recorded yet |
source_reference | string | What added it, such as api_key:<id>, user:<id>, or sms:<message id> for a carrier refusal |
blocks | boolean | Whether ThunderPhone refuses texts or calls for this row. false for carrier rows |
created_at, updated_at | timestamp | ISO 8601 UTC |
Carrier opt-outs
When Twilio or Telnyx refuses a text because the recipient opted out with the
carrier (error code recipient_opted_out), ThunderPhone records a carrier
row for that number. The carrier enforces these itself, so a carrier row is
recorded but not enforced by ThunderPhone (blocks: false): it skips the
scheduled texts already queued to that number, but new texts are passed to the
carrier as usual. That way a person who opts back in with the carrier, for
example by texting START, is not kept blocked here. A carrier row is removed
when the carrier next accepts a text to that number.
Adding the same number through the API or dashboard turns a carrier row into
a blocking one.
List opt-outs
curl 'https://api.thunderphone.com/v1/opt-outs?channel=sms' \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"| Parameter | Description |
|---|---|
number | One number, in any format the API can read as a phone number |
channel | sms or call |
source | api, manual, carrier or inbound_stop |
limit | 1–500, default 50 |
offset | Default 0 |
Results are newest first.
{
"count": 1,
"results": [
{
"id": 318,
"number": "+14155550199",
"channel": "sms",
"source": "api",
"source_reference": "api_key:12",
"blocks": true,
"created_at": "2026-11-01T18:02:11.204Z",
"updated_at": "2026-11-01T18:02:11.204Z"
}
]
}Add a number
curl -X POST https://api.thunderphone.com/v1/opt-outs \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"number": "+14155550199", "channel": "both"}'| Field | Type | Required | Description |
|---|---|---|---|
number | string | yes | The number, E.164 |
channel | string | no | sms, call or both (default) |
Returns the number's row for each channel, 201 Created when any row is new
and 200 OK when the number was already on the list:
{
"results": [
{ "id": 318, "number": "+14155550199", "channel": "sms", "source": "api", "source_reference": "api_key:12", "blocks": true, "created_at": "2026-11-01T18:02:11.204Z", "updated_at": "2026-11-01T18:02:11.204Z" },
{ "id": 319, "number": "+14155550199", "channel": "call", "source": "api", "source_reference": "api_key:12", "blocks": true, "created_at": "2026-11-01T18:02:11.204Z", "updated_at": "2026-11-01T18:02:11.204Z" }
]
}A number that is not a valid phone number returns 400 invalid_number.
Remove a number
curl -X DELETE 'https://api.thunderphone.com/v1/opt-outs?number=%2B14155550199&channel=sms' \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"number is required; channel is sms, call or both (default). Remove a
number only when the person has opted back in.
{ "deleted": 1 }deleted is the number of rows removed, 0 when the number was not on the
list.
What a refusal looks like
| Request | Response |
|---|---|
POST /v1/sms | 409 with error.code contact_opted_out |
POST /v1/scheduled | 409 with error.code contact_opted_out |
POST /v1/call | 400 destination_not_allowed with reason contact_opted_out |
| Campaign contact | The contact fails; the campaign keeps running |
| Scheduled send already queued | skipped with status_reason opted_out |