Bitcall logoBitcall API
The Gateway

Errors

One error shape, thirteen codes, and what to do about each.

Every failure — from any product, at any layer — has the same shape:

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Your account balance is too low for this operation.",
    "requestId": "9f1c7b2e-4a83-4d21-9c55-1f0b3a7e2d64"
  }
}

Branch on code, never on message. The code is contract; the message is prose we may improve. requestId is the same value as the x-request-id response header — quote it when you contact support and we can find your exact request.

fields appears on validation failures only, naming the offending fields without repeating their values:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request is not valid. See `fields` for what to correct.",
    "requestId": "…",
    "fields": ["body.msisdns", "query.pageSize"]
  }
}

The codes

These thirteen are the whole vocabulary. Nothing else is ever returned — and note there is no 403: a permissions failure answers 401 like any other credential problem.

Your request needs changing

CodeHTTPWhat to do
VALIDATION_ERROR400Fix the fields listed in fields. Retrying unchanged will fail identically.
IDEMPOTENCY_KEY_REQUIRED400Add an Idempotency-Key header — see Idempotency.
NOT_FOUND404The identifier does not exist, or is not yours. See below.
CONFLICT409The resource is not in a state that allows this — an order already cancelled, a job already finished.
IDEMPOTENCY_CONFLICT409You reused an idempotency key with a different request body. Use a new key.

Your credentials

CodeHTTPWhat to do
AUTHENTICATION_REQUIRED401You sent no credentials. Add x-key-id and x-api-secret.
INVALID_API_KEY401The credentials you sent were not accepted.

INVALID_API_KEY is deliberately one answer to several situations: a key that does not exist, a wrong secret, a revoked key, an IP outside the key's allowlist, and a key without the permissions this operation needs. Distinguishing them would let someone probe which key ids are real without holding a secret.

So if your credentials are definitely correct, the likely cause is permissions — check with whoever issued the key. Quote the requestId and we can tell you exactly which check failed.

Something is unavailable

CodeHTTPWhat to do
INSUFFICIENT_BALANCE409Top up. Retrying without doing so will fail identically.
PRODUCT_UNAVAILABLE409This product cannot be sold right now. Try another, or retry later.
RATE_LIMITED429Wait for the interval in the Retry-After header, then retry.
SERVICE_UNAVAILABLE503A dependency is temporarily down. Retry with backoff.
SERVICE_RESPONSE_INVALID502A downstream service answered in a way that does not match our contract. We rejected it rather than pass it on. Retry; if it persists, report it with the requestId.
INTERNAL_ERROR500Our bug. Retry once, then report it with the requestId.

Which errors are worth retrying

BehaviourCodes
Do not retry — the outcome will not changeVALIDATION_ERROR · IDEMPOTENCY_KEY_REQUIRED · IDEMPOTENCY_CONFLICT · NOT_FOUND · CONFLICT · AUTHENTICATION_REQUIRED · INVALID_API_KEY
Retry after actingINSUFFICIENT_BALANCE (top up) · RATE_LIMITED (wait for Retry-After)
Retry with backoffSERVICE_UNAVAILABLE · SERVICE_RESPONSE_INVALID · INTERNAL_ERROR · PRODUCT_UNAVAILABLE

When retrying anything that spends money, send the same Idempotency-Key as the original attempt. That is what makes a retry safe rather than a second charge — see Idempotency.

A 404 never confirms someone else's data

If you ask for an identifier that belongs to another account, you get exactly the same 404 NOT_FOUND as for an identifier that has never existed. The two are indistinguishable on purpose: an answer that distinguished them would confirm that a guessed order id is real and belongs to somebody.

So a 404 means "not yours, or not a thing" — never "exists, but you may not see it".

On this page