On this page

Errors

Beta

One shape for every refusal, a stable code to branch on, and an id to quote.

2 min read

Every failure is an RFC 9457 problem, sent as application/problem+json:

{
  "type": "https://ghostsms.io/docs/api/errors/insufficient_balance",
  "title": "Insufficient balance",
  "status": 402,
  "code": "insufficient_balance",
  "detail": "This purchase needs $0.62 and the balance is $0.11.",
  "request_id": "req_8f3a2c"
}

Branch on code, never on title or detail. The code is stable and part of the contract; the sentences are written for people and will be improved.

Quote request_id to support. It identifies the one request in our logs, and it is on every response — successful ones too — as the Request-Id header.

Some problems carry an extra field that tells you what to do next, such as retry_after_seconds on a 429, required_scope on an insufficient scope, or activation_id on a duplicate.

The codes you will actually handle

StatusCodeWhat it means
401authentication_requiredNo key, or not a key — a website token gets this too
401api_key_revokedThe key was revoked, or the account's access was withdrawn
403insufficient_scopeReal key, wrong scopes; carries required_scope
403daily_spend_limit_reachedThe account's daily spend ceiling; carries resets_at
402insufficient_balanceTop up and retry with the same idempotency key
409duplicate_pending_activationYou already have one open for this pair; carries activation_id
409number_unavailableNothing free for that service and country right now
409not_yet_availableToo early — no_code_available_at has not passed
409request_conflictThe resource moved on: already cancelled, already refunded
422validation_failedA field is missing or malformed; the body names it
429rate_limitedCarries Retry-After and retry_after_seconds

404 is also how we answer a resource that belongs to somebody else — a key never learns whether an id it cannot see exists.

Retrying

429 and 5xx are worth retrying, with backoff. 4xx otherwise is not: the request needs changing, not repeating. When you do retry a purchase, send the same idempotency key so the retry cannot buy twice.