ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Phone Numbers

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 sms are refused: POST /v1/sms, the agent's send_sms tool, post-call follow-ups, retries and scheduled texts.
  • Calls to a number opted out of call are refused: POST /v1/call, campaigns and scheduled calls.
  • Adding a number skips the queued scheduled sends to it on that channel, with status skipped and reason opted_out.

Use these endpoints to keep the list in step with your own CRM's opt-outs.

Endpoints

MethodPathDescription
GET/v1/opt-outsList opt-outs
POST/v1/opt-outsAdd a number
DELETE/v1/opt-outsRemove 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"
}
FieldTypeDescription
idintegerId of the row
numberstringE.164
channelstringsms or call. A number opted out of both has one row for each
sourcestringWhere 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_referencestringWhat added it, such as api_key:<id>, user:<id>, or sms:<message id> for a carrier refusal
blocksbooleanWhether ThunderPhone refuses texts or calls for this row. false for carrier rows
created_at, updated_attimestampISO 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"
ParameterDescription
numberOne number, in any format the API can read as a phone number
channelsms or call
sourceapi, manual, carrier or inbound_stop
limit1–500, default 50
offsetDefault 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"}'
FieldTypeRequiredDescription
numberstringyesThe number, E.164
channelstringnosms, 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

RequestResponse
POST /v1/sms409 with error.code contact_opted_out
POST /v1/scheduled409 with error.code contact_opted_out
POST /v1/call400 destination_not_allowed with reason contact_opted_out
Campaign contactThe contact fails; the campaign keeps running
Scheduled send already queuedskipped with status_reason opted_out