Errors
{ "error": { "type": "insufficient_scope", "message": "…", "scope": "sms:send" } }
| Field | Contract |
|---|---|
type | Stable. Branch on this. New values may appear; an existing one is never repurposed. |
message | Human-readable, may change at any time. Never parse it. |
scope | Present on insufficient_scope — exactly which capability to request next. |
The catalog
type | Status | What happened | What to do |
|---|---|---|---|
invalid_request | 400 | Missing or malformed field, unknown enum value, unknown field in the body, or unparseable JSON | Fix the request. The message names the field. |
unauthenticated | 401 | No credential, or one that is invalid, revoked or expired | Refresh the token, or check the key |
insufficient_scope | 403 | Valid credential, missing scope | Re-authorize with the scope in the scope field |
permission_denied | 403 | Scope is fine — the person's role lacks the permission | Their admin changes the role in the portal |
not_found | 404 | No such object, or it is not this account's | Check the id |
conflict | 409 | The request contradicts current state | Re-read, then retry |
unsupported | 400 | A recognized parameter with a value this API cannot honour | See the message — e.g. enabled=false as a filter |
rate_limited | 429 | Too many requests | Back off. Not currently emitted — see Rate limits. |
unavailable | 503 | A dependency is down | Retry with backoff |
internal_error | 500 | Something failed on our side | Retry. You were not charged. |
The two 403s are not the same problem
This is the single most common source of lost time on this API.
{ "error": { "type": "insufficient_scope", "message": "credential is not authorized for sms:send", "scope": "sms:send" } }
→ Your code's problem. The credential was never granted sms:send.
Re-authorize, or add the scope to the key.
{ "error": { "type": "permission_denied", "message": "the user who authorized this application does not have access to call recordings" } }
→ Not your code's problem. The scope is present; the user behind the token lacks the matching admin-center permission. No amount of re-authorizing fixes it — their administrator has to change their role. See Scopes.
401 names the scope it wanted
Every challenge carries an RFC 6750 header, so a spec-compliant client can ask for exactly the right grant next time instead of over-asking:
WWW-Authenticate: Bearer realm="Telebroad API", error="invalid_token", scope="sms:read"
What is safe to retry
| Status | Safe to retry | Why |
|---|---|---|
5xx | Yes | Nothing was created or charged. Guaranteed. |
429 | Yes, after backing off | Nothing happened |
4xx | No — fix the request first | It will fail identically |
Two writes are idempotent, so a retry after a timeout is safe even if the first attempt succeeded:
POST /sms/conversations/{line}/{number}/resolve— re-resolving does not re-stamp who closed it or when.POST /webhooks/{id}/enableand/disable— setting the state it is already in is a no-op.
POST /sms/messages is not idempotent. A retry after a timeout may send and
bill a second message. If you cannot tolerate that, check
SMS history before retrying.
5xx messages are deliberately vague
An internal error always reads "internal error". Internal messages carry table
names, SQL fragments and hostnames, so they are logged on our side rather than
returned. If you need one traced, send us the timestamp, the endpoint and the
account — every one is logged with a correlation code.