docs

Docs › Reference

Errors

Every error is the same envelope: a machine code to branch on, a human message, and a docs_url that links to the code's row on this page.

error.json
{
  "error": {
    "code": "recipient_opted_out",
    "message": "This recipient texted STOP. You cannot message them again unless they text START.",
    "docs_url": "https://docs.handset.dev/errors.html#recipient_opted_out"
  }
}

HTTP status carries the class of failure: 400/422 your request needs a change, 401/403 credentials, 404 no such resource (in your account — IDs are account-scoped), 409 conflict, 429 slow down, 5xx us — retry.

Authentication & limits

missing_api_keyNo Authorization header. Pass your key as a bearer token: Authorization: Bearer hs_live_….
malformed_authorizationThe header isn't Bearer <key>. Check the scheme and whitespace.
invalid_api_keyUnknown, malformed, or revoked key. Check it in the console; test and live are different keys.
account_suspendedThe account is suspended. Contact support to restore access.
tenant_restricted_keyThis key is scoped to one tenant and the request touched another. Use an account-wide key or the right tenant's key.
rate_limitedOver 20 req/s (burst 60) on this key. The SDKs retry this automatically honoring Retry-After; if you see it, you're sustainedly over — spread the work out.

Request shape

invalid_jsonThe body isn't valid JSON.
invalid_bodyThe body doesn't match the endpoint's schema — the message names the field.
unreadable_bodyThe body couldn't be read (truncated or oversized request).
missing_fieldsA required field is absent — the message lists which.
not_foundNo such resource in your account. IDs are account-scoped: another account's ID 404s rather than 403s.
idempotency_key_reuseThis Idempotency-Key was already used with a different body. Reuse a key only to retry the identical request.
invalid_startUnparseable start timestamp — use RFC 3339.
invalid_endUnparseable end timestamp — use RFC 3339.
invalid_rangestart must be before end.
internal_errorSomething failed on our side. Retry; contact support if it persists.
database_unavailableBrief outage on our side. Retry with backoff.

Messaging

empty_messageProvide body, media_urls, or both.
body_too_longThe body exceeds the maximum length. Split the message.
invalid_toThe destination isn't a valid E.164 phone number (+14155550123).
recipient_opted_outThey texted STOP. Do not retry — delivery is blocked until they text START. Design for it: check /v1/opt_outs or handle this code.
from_number_not_foundThe from number isn't one of this tenant's numbers.
number_not_sms_capableThis number can't send SMS (voice-only).
campaign_not_approvedThe number's 10DLC campaign is still in review. Test mode approves instantly; live approval takes days.
message_not_foundNo such message in your account.
conversation_not_foundNo such conversation in your account.

Numbers & porting

invalid_phoneNot a valid E.164 phone number.
number_unavailableThis number was just taken. Search again and pick another.
phone_number_not_foundNo such number in your account.
invalid_phone_numbersThe list is empty or contains non-E.164 entries.
numbers_not_portableAt least one number can't be ported — the message lists which. Run POST /v1/port_ins/check first.
port_in_not_foundNo such port-in in your account.
port_in_not_submittableOnly draft port-ins can be submitted.
port_in_not_cancellableThis port-in has progressed past the point of cancellation.
invalid_service_addressThe service address is incomplete — street, city, state, and ZIP are required.
e911_address_invalidThe address failed E911 validation. Verify it with the postal service format.

Voice

invalid_to_numberThe customer number to dial isn't valid E.164.
invalid_connect_toThe agent number to bridge isn't valid E.164.
call_not_activeDTMF and gather commands need an in-progress call.
destination_not_supportedThe exchange is a known high-cost destination (access stimulation) and can't be dialed — from any leg or ring target.
call_not_foundNo such call in your account.
voicemail_not_foundNo such voicemail in your account.
recording_not_foundNo such recording in your account.
routing_config_not_foundNo such routing config in your account.
routing_config_in_useNumbers still reference this routing config — detach them first.

10DLC compliance

brand_not_foundNo such brand in your account.
brand_not_vettedThe brand hasn't completed vetting; campaigns need a vetted brand.
campaign_not_foundNo such campaign in your account.
invalid_einThe EIN must be nine digits (12-3456789 accepted).
invalid_entity_typeEntity type must be one of the documented values (e.g. private_profit).
invalid_billing_phone_numberThe brand contact phone isn't valid E.164.
invalid_contact_emailThe brand contact email isn't a valid address.
invalid_use_caseUse case must be one of the documented campaign use cases.
description_requiredCampaigns need a description of the traffic.
invalid_sample_messagesProvide 1–5 realistic sample messages.

Tenants & webhooks

tenant_not_foundNo such tenant in your account.
name_requiredTenants need a display name.
invalid_timezoneNot an IANA timezone (America/Phoenix).
external_ref_takenAnother tenant already carries this external_ref. They're unique per account.
webhook_endpoint_not_foundNo such webhook endpoint in your account.
url_requiredWebhook endpoints need a URL.
invalid_urlThe webhook URL must be a valid https:// address.
unknown_event_typeThe event type filter names an event we don't emit — see Webhooks for the list.