API essentials

Errors

Read one error format across the whole API, branch on a small set of error types, and know from the status code whether anything was processed before it failed.

Every error has the same shape:

{
  "error": {
    "type": "invalid_request_error",
    "message": "currency must be a 3-letter ISO code"
  }
}

Branch on type and the HTTP status. Write message to your logs, but never branch on it — we word it for humans and we will reword it.

Some errors carry an extra code naming the specific cause, such as requires_action on a payment that needs 3-D Secure.

Branch on the error type

TypeStatusWhat it means
invalid_request_error400Your request is malformed, or asks for something the current state does not allow. Retrying it unchanged will not help.
authentication_error401Your API key is missing, malformed, revoked, or in the wrong mode.
permission_error403You authenticated, but you may not do this.
conflict_error409Your request conflicts with the resource's current state — a duplicate id, or an already-voided invoice.
idempotency_error409You reused an Idempotency-Key with a different body, or named a request tmbilling cannot safely replay. See Idempotency.
rate_limit_error429You are over the per-key limit. tmbilling processed nothing. See Rate limits.
payment_error400 / 402The gateway refused, or wants your buyer to authenticate. A 402 carries a payment_intent and a next_action.
api_error500Something failed on our side.

Decide whether to retry

The status tells you what your client should do next:

StatusWhat to do
400, 403, 409Don't retry. The request fails the same way every time — fix the input or the state first. A 409 on a duplicate id means the resource exists, so fetch it.
401Don't retry blindly. Check the key and its mode first. A retry loop on a revoked key is noise.
429Retry after the delay. tmbilling processed nothing, so wait for Retry-After and send the same request again.
500 or a network timeoutRetry with the same Idempotency-Key. The outcome is unknown, and this is exactly what idempotency exists for.

Treat a declined card as billing state, not an error

A declined card is not a bug in your request. Your API call succeeded. The charge failed.

You see it as a payment_error — with 402 and a next_action when your buyer only has to authenticate — and, more usefully, as state on the objects. The invoice moves to payment_due, a transaction row records the attempt with the gateway's own error code, and dunning starts. tmbilling drops no gateway refusal. Each one is a row you can query.

See Failed payments and dunning for what happens next and what you configure.

Read validation errors

A body that fails validation returns 400 with a message naming the field.

An unknown query parameter also returns 400 rather than being ignored. So a typo in a filter name tells you, instead of handing back an unfiltered list that looks plausible.

Copyright © 2026