Errors
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
| Type | Status | What it means |
|---|---|---|
invalid_request_error | 400 | Your request is malformed, or asks for something the current state does not allow. Retrying it unchanged will not help. |
authentication_error | 401 | Your API key is missing, malformed, revoked, or in the wrong mode. |
permission_error | 403 | You authenticated, but you may not do this. |
conflict_error | 409 | Your request conflicts with the resource's current state — a duplicate id, or an already-voided invoice. |
idempotency_error | 409 | You reused an Idempotency-Key with a different body, or named a request tmbilling cannot safely replay. See Idempotency. |
rate_limit_error | 429 | You are over the per-key limit. tmbilling processed nothing. See Rate limits. |
payment_error | 400 / 402 | The gateway refused, or wants your buyer to authenticate. A 402 carries a payment_intent and a next_action. |
api_error | 500 | Something failed on our side. |
Decide whether to retry
The status tells you what your client should do next:
| Status | What to do |
|---|---|
400, 403, 409 | Don'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. |
401 | Don't retry blindly. Check the key and its mode first. A retry loop on a revoked key is noise. |
429 | Retry after the delay. tmbilling processed nothing, so wait for Retry-After and send the same request again. |
500 or a network timeout | Retry 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.
Related
Idempotency
Send an Idempotency-Key on every write so a retry after a timeout cannot charge twice, and learn what happens when you reuse a key with a different body.
Pagination
Page through any list with cursors, filter it with one field[operator]=value grammar, and sort or search it. There is no catch-all filter parameter to guess.