Errors

Every error Settlane returns has the same shape and a stable, machine-readable code. Branch on the code, never on the message — messages are written for a person and may be reworded; codes are a contract.

{ "error": { "code": "IDEMPOTENCY_CONFLICT", "message": "…", "param": "amount_cents" } }

param is present only when the API can name one request field as the cause.

Every response also carries a Settlane-Request-Id header. Quote it in a support request: for an INTERNAL error it is the only thing that identifies what happened, because the body deliberately carries no detail.

Every code a /v1 call can return

The codes and their statuses below are generated from the API's own code. The "What it means" column is written by hand.

On the /v1 API

codeStatusWhat it means
UNAUTHORIZED401No API key, a malformed one, or one that has been revoked. Check the Authorization: Bearer sk_... header.
FORBIDDEN403The key is genuine but lacks the scope this route needs. Issue a key with the scope in the dashboard. On POST /v1/payouts/{id}/approve and .../reject it can also mean the workspace has approval through the API switched off (param is api_approval_enabled), that the payout is above an API-approval cap (param is api_approval_max_cents or api_approval_daily_cents), or that it is waiting for identity verification, which only the dashboard approves (param is hold_reason); the payout is left waiting for a person.
NOT_FOUND404No object with that id exists for this tenant and mode. A live key never sees a test object, and the reverse.
INVALID_REQUEST400The request body or query failed validation. param names the field when one field is at fault.
CONFLICT409The object is not in a state this action can be taken from -- cancelling an invoice that is already paid, for instance.
IDEMPOTENCY_CONFLICT422This Idempotency-Key was already used with a DIFFERENT request body. Use a fresh key, or resend the identical body.
RATE_LIMITED429Too many requests. Back off and retry; Retry-After says how long.
TENANT_NOT_ACTIVE403The tenant has not finished activation, so it may not create invoices or payouts yet. Finish the checklist on the dashboard's Home page.
FUNDS_INSUFFICIENT409An invoice created with pay_from_funds was refused because the customer's funds in that asset do not cover it. Nothing was created and the customer's open invoice was left as it was.
TEST_MODE_UNAVAILABLE400A sk_test_ key reached a route whose test-mode runtime this deployment does not have.
LIVE_MODE_REQUIRED403This route changes LIVE configuration and a sk_test_ key was used. Send the same request with the live key.
ROW_POLICY_VIOLATION500The database refused a write as outside this connection's tenant or mode. Always a defect in Settlane -- report it with the request id.
INTERNAL500Something failed inside Settlane. The response body carries no detail by design; quote the Settlane-Request-Id header.
IDENTITY_VERIFICATION_UNAVAILABLE503Identity verification is not configured for this tenant. Fail closed: no check ever passes by being skipped.
IDENTITY_PROVIDER_FAILED502The identity provider threw or answered non-2xx. Retry; nothing about the customer has changed.
IDENTITY_IN_REVIEW409This customer's verification is with a human reviewer. Nothing to do but wait for the decision.
IDENTITY_UNDER_AGE409The verified date of birth is below the age gate. Terminal for this customer.
IDENTITY_ATTEMPTS_EXHAUSTED409This customer has used every verification attempt allowed in the current window.
IDENTITY_RETRY_TOO_SOON409The customer is inside the cool-down after a failed attempt. Retry after it expires.
IDENTITY_NOT_VERIFIED403The action needs a verified customer and this one is not verified.
IDENTITY_VERIFICATION_NOT_FOUND404No verification with that id exists for this tenant.
CUSTOMER_BLOCKED409The customer is blocked by an operator. No verification is started and no payout is made for a blocked customer.
VERIFICATION_DOCUMENT_GONE409?include=document_dob was asked after the provider session was deleted. The date of birth is never stored, so there is nothing left to read.
IDENTITY_DATE_OF_BIRTH_INVALID400expected_date_of_birth is not a date this system will guess at. Send an ISO YYYY-MM-DD.
IDENTITY_PROVIDER_SCHEMA_UNVERIFIED503Settlane cannot read the identity provider's decisions safely right now, so it decides nothing. Retry later, and contact support if it lasts.
PAYOUT_ADDRESS_NOT_PERMITTED403This destination address is not permitted. Deliberately uninformative -- it never says which list answered.
PAYOUT_ADDRESS_BLOCKED409The destination is on this tenant's own block list or the platform's global one.
PAYOUT_ADDRESS_SANCTIONED403The destination is on a sanctions list. Do not retry and do not route around it.
PAYOUT_ADDRESS_TETHER_FROZEN409Tether has frozen this address. Tokens sent to it cannot be moved again.
PAYOUT_ADDRESS_CIRCLE_FROZEN409Circle has frozen this address. Tokens sent to it cannot be moved again.
SANCTIONS_LIST_STALE503Settlane's sanctions list is older than its freshness window, so no payout is screened against it. Retry.
TETHER_CHECK_UNAVAILABLE503The Tether freeze-list check could not run. Settlane's outage, not a refusal of the customer -- retry.
CIRCLE_CHECK_UNAVAILABLE503The Circle freeze-list check could not run. Settlane's outage, not a refusal of the customer -- retry.
PAYOUT_ADDRESS_COUNTERPARTY_RISK409A counterparty-risk provider refused this address. Not reachable today -- no such provider is integrated.
COUNTERPARTY_RISK_CHECK_UNAVAILABLE503The counterparty-risk check could not answer, which is never read as clean. Settlane's outage -- retry.
API_KEY_IP_NOT_ALLOWED403This key has an IP allow-list and this request's source address is not on it, or could not be proved.

36 codes.

The ones worth knowing before you ship

IDEMPOTENCY_CONFLICT (422) is not a rate limit and not a duplicate. It means you reused an Idempotency-Key with a different body. Resending the identical body replays the stored response; changing the body under the same key is refused, because one of the two requests would otherwise be silently discarded.

NOT_FOUND (404) covers "not yours" and "does not exist" identically. A live key asking for a test object gets a 404, and so does a key asking for another tenant's object. That is deliberate: a distinguishable "exists but not yours" is an enumeration oracle.

RATE_LIMITED (429) carries Retry-After, in seconds. Wait that long and try again. Retry a create only when it is safe: with the same Idempotency-Key for POST /v1/invoices, or with the same reference for a payout and the same external_id for a customer, which make those creates idempotent on their own.

INTERNAL (500) has a fixed message, always. The real message is for our logs; it may hold a SQL fragment or a third party's error text, neither of which is vetted to show you. There is nothing to parse in it.

A timeout is not a failure — it is an unknown. The request may have been fully applied. Recovering from one means re-sending the same Idempotency-Key, which makes the second attempt a lookup rather than a second object. Never retry a create on a timeout with a new key.

A response that is not JSON came from something in front of the API, such as a proxy or a gateway page. Treat it as a temporary failure, and do not show its body to your users.

Next