# Errors

Every failure answers JSON in the same shape, whether it is a validation problem, an auth
problem, a missing record, a provider outage or a path that does not exist:

```json
{
  "error": "The request does not match the published schema",
  "code": "invalid_request"
}
```

Branch on `code`. It is a stable token and will not change once we have published it. The `error`
field is prose for whoever is reading a log, and we reword it whenever a clearer sentence turns
up.

Each operation in the [reference](/reference) lists the codes it can carry. We add codes as new
conditions become worth telling apart, and we never remove one or change what it means, so keep a
default branch for anything you do not recognise.

## Codes any operation can answer

| Code | Status | Meaning |
| --- | --- | --- |
| `invalid_request` | 400 | the body or path does not match the published schema |
| `unreadable_request` | 400 | the body is not the media type it claims |
| `unauthorized` | 401 | missing, malformed or revoked key |
| `payload_too_large` | 413 | over the body limit |
| `upstream_failed` | 502 | us, or a provider we depend on |
| `unknown_endpoint` | 404 | this API serves no such path |

`unknown_endpoint` does not belong to any operation, so you will not find it listed under one in
the reference. It is what a mistyped path answers, and it comes back in the same shape as
everything else rather than as plain text your client cannot parse.

## Codes worth handling by name

The not-found codes are `business_not_found`, `invoice_not_found`, `import_not_found` and
`subscription_not_found`. In practice these usually mean an identifier you have not synced yet
rather than one that does not exist anywhere.

Taking an advance has three refusals of its own, all `409`: `invoice_not_financeable`,
`business_not_eligible` and `concentration_exceeded`. [Financing an
invoice](/financing-an-invoice) explains what each one means.

Two codes show up when you replay something that already happened: `already_subscribed` and
`already_imported`.

The rest name input we can be specific about. `invalid_routing_number` and `invalid_invitee` are
self-explanatory. `self_dealing` means a business tried to finance its own invoice.
`unsupported_media_type` and `unreadable_file` come from imports, as does `too_many_rows`, since
an import is capped at 1000. `destination_refused` means a webhook URL we will not send to.

## Retrying

`502` is the only one worth retrying blind, with a backoff behind it. Anything in the `4xx` range
will keep answering the same way until either the request or the underlying state changes.

If a request times out and you never saw the answer, retrying is safe for any write keyed on your
own identifier: syncing a business, syncing an invoice, setting a payout account, taking an
advance, running an import. Those update rather than duplicate.

Subscribing to a webhook destination is the one call where that is not true. If the first
`POST /v1/webhooks` committed and you lost the response, the retry answers `409
already_subscribed`, and the signing secret came back on that first response and is never
returned again, including by [`GET /v1/webhooks`](/reference/webhooks#list-your-subscriptions). You end up with a
subscription whose deliveries you cannot verify. To recover, take the reference from the listing,
delete it with [`DELETE /v1/webhooks/{reference}`](/reference/webhooks#stop-sending-to-a-destination),
and subscribe again, keeping the secret this time.
