API ReferenceBilling

Errors

Every status and error code the billing API answers with, and what to do about each.

Errors

Every failure answers in the same shape:

{
  "message": "The request has invalid fields.",
  "error": "VALIDATION_FAILED",
  "errors": [
    {
      "field": "line_items[2].amount",
      "code": "TOO_MANY_DECIMALS",
      "message": "USD amounts have at most 2 decimal places"
    }
  ],
  "request_id": "req_01JQ8Z0Y5N6W3K2M4P7R9T1V3X"
}
  • error — a stable code. It is a contract: branch on it in your integration. Codes are added over time, never renamed or repurposed.
  • message — a fixed human sentence. It never contains your data.
  • errors — present only on VALIDATION_FAILED and UNSUPPORTED_IN_V1. Every problem with the request, each naming the exact path into your body.
  • request_id — also in the X-Request-Id header on every response. Quote it to support and we can find the exact request.

A code always answers with the same status.

400 — fix the request

CodeWhat it means
VALIDATION_FAILEDOne or more fields are invalid. errors lists all of them.
UNSUPPORTED_IN_V1The request uses a feature that is not supported yet — a subscription set, or a usage_based_recurring line. errors names the fallback for each.
INVALID_JSONThe body is not valid JSON.
BAD_REQUESTThe request could not be read.

Validation reports every problem at once, so one round trip tells you everything to fix. Common field codes inside errors: REQUIRED, INVALID_TYPE, INVALID_FORMAT, TOO_LONG, TOO_MANY_DECIMALS, TOO_LARGE, OUT_OF_RANGE, MUST_BE_POSITIVE, MUST_NOT_BE_NEGATIVE, DUPLICATE, UNKNOWN_FIELD, FIELD_NOT_ALLOWED_FOR_TYPE, TOO_FEW_ITEMS, TOO_MANY_ITEMS, UNSUPPORTED_CURRENCY.

401 / 404 / 405

CodeWhat it means
UNAUTHORIZEDThe API key is missing or invalid.
NOT_FOUNDThe object in the path does not exist, or the route does not.
METHOD_NOT_ALLOWEDThat method is not allowed here. The Allow header lists what is.

409 — valid, but it conflicts with what is stored

These are the "you cannot change that" family. The request was fine; the stored object will not accept it.

CodeWhat it meansWhat to do
INVOICE_LOCKEDThe invoice has a payment, refund, void or write-off, so its billed content is frozen.Void it and send a corrected invoice.
INVOICE_CURRENCY_LOCKEDAn invoice's currency cannot change once it exists.Void it and send a new one.
INVOICE_VOIDEDA voided invoice cannot take transactions.Record the money against a live invoice.
PLAN_CADENCE_LOCKEDInvoices already use this plan's billing interval.Create a new plan.
TRANSACTION_IMMUTABLEA transaction's type, amount, currency, invoice or parent cannot change.Record a reversal instead.
EVENT_IMMUTABLEA subscription event cannot change once recorded.Send a retraction.
SUBSCRIPTION_BELONGS_TO_ANOTHER_CUSTOMERThat subscription id is already another customer's.Use an id unique to this customer.
TRANSACTION_EXISTS_IN_PAYMENTS_APIThat id was already recorded through the payments API.Use a different id, or keep that payment where it is.
IMPORT_NOT_OPENThe import no longer accepts items, a commit or a cancel.Open a new import for whatever is left.
IMPORT_TOO_LARGEThe import has staged as much data as one import may.Commit it and open another.
TOO_MANY_OPEN_IMPORTSThe site already holds 3 open imports.Commit or cancel one first.
CONCURRENT_UPDATEAnother request is writing the same object right now.Retry shortly.

413 / 415

CodeWhat it means
PAYLOAD_TOO_LARGEThe body is over 1 MB (5 MB on the import routes). Send fewer line items or fewer bulk items per call.
UNSUPPORTED_MEDIA_TYPEThe body must be sent as Content-Type: application/json.

422 — it names something that is not there

The request is well-formed, but it refers to an object that does not exist or breaks a rule across objects. Usually a sequencing problem: send the thing it names first.

CodeWhat to do
CUSTOMER_NOT_FOUNDSend the customer first.
PLAN_NOT_FOUNDSend the plan first.
INVOICE_NOT_FOUNDSend the invoice first.
SUBSCRIPTION_NOT_FOUNDOnly a subscription_start event or an invoice line can create a subscription. Send one of those first.
TRANSACTION_NOT_FOUNDThe payment a reversal names does not exist, did not succeed, or is on another invoice.
EVENT_NOT_FOUNDThe event a retraction names does not exist.
CURRENCY_MISMATCHThe currency you sent is not the invoice's. Omit it, or send the invoice's.
AMOUNT_EXCEEDS_INVOICEPayments on an invoice cannot exceed its total.
AMOUNT_EXCEEDS_PAYMENTRefunds, chargebacks and reversals cannot exceed the payment they act on.
CURRENCY_NOT_SUPPORTEDFaurya holds no exchange rate for that currency.

429 — rate limited

CodeWhat it means
RATE_LIMITEDOver 25 requests/second (bursting to 100) for your site. The Retry-After header says how many seconds to wait.

If you are hitting this regularly you are probably loading a history. Use the bulk import instead.

500 / 503 — ours

CodeWhat it means
INTERNAL_ERRORSomething went wrong on our side. Your request was not saved. Retry; if it keeps failing, contact support with the request_id.
SERVICE_UNAVAILABLEWe are temporarily busy. Your request was not saved. Retry after the Retry-After header.

Both are safe to retry: writes are idempotent on your external_id, so a retry of a request that actually did land is a replay, not a duplicate.

Retrying safely

  • Every write is a PUT keyed on an id you chose, so retrying is always safe. Sending the same body again writes nothing and answers 200.
  • Retry 429, 500 and 503, honouring Retry-After where it is present.
  • Do not retry 400, 409, 415 or 422 unchanged — they will fail the same way. 422 is usually fixed by sending the object it names first; 409 means the stored object genuinely will not take that change.
  • Log the request_id with the response status in your backend. It is the one value that joins your logs to ours.