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
code | Status | What it means |
|---|---|---|
UNAUTHORIZED | 401 | No API key, a malformed one, or one that has been revoked. Check the Authorization: Bearer sk_... header. |
FORBIDDEN | 403 | The 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_FOUND | 404 | No object with that id exists for this tenant and mode. A live key never sees a test object, and the reverse. |
INVALID_REQUEST | 400 | The request body or query failed validation. param names the field when one field is at fault. |
CONFLICT | 409 | The object is not in a state this action can be taken from -- cancelling an invoice that is already paid, for instance. |
IDEMPOTENCY_CONFLICT | 422 | This Idempotency-Key was already used with a DIFFERENT request body. Use a fresh key, or resend the identical body. |
RATE_LIMITED | 429 | Too many requests. Back off and retry; Retry-After says how long. |
TENANT_NOT_ACTIVE | 403 | The tenant has not finished activation, so it may not create invoices or payouts yet. Finish the checklist on the dashboard's Home page. |
FUNDS_INSUFFICIENT | 409 | An 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_UNAVAILABLE | 400 | A sk_test_ key reached a route whose test-mode runtime this deployment does not have. |
LIVE_MODE_REQUIRED | 403 | This route changes LIVE configuration and a sk_test_ key was used. Send the same request with the live key. |
ROW_POLICY_VIOLATION | 500 | The database refused a write as outside this connection's tenant or mode. Always a defect in Settlane -- report it with the request id. |
INTERNAL | 500 | Something failed inside Settlane. The response body carries no detail by design; quote the Settlane-Request-Id header. |
IDENTITY_VERIFICATION_UNAVAILABLE | 503 | Identity verification is not configured for this tenant. Fail closed: no check ever passes by being skipped. |
IDENTITY_PROVIDER_FAILED | 502 | The identity provider threw or answered non-2xx. Retry; nothing about the customer has changed. |
IDENTITY_IN_REVIEW | 409 | This customer's verification is with a human reviewer. Nothing to do but wait for the decision. |
IDENTITY_UNDER_AGE | 409 | The verified date of birth is below the age gate. Terminal for this customer. |
IDENTITY_ATTEMPTS_EXHAUSTED | 409 | This customer has used every verification attempt allowed in the current window. |
IDENTITY_RETRY_TOO_SOON | 409 | The customer is inside the cool-down after a failed attempt. Retry after it expires. |
IDENTITY_NOT_VERIFIED | 403 | The action needs a verified customer and this one is not verified. |
IDENTITY_VERIFICATION_NOT_FOUND | 404 | No verification with that id exists for this tenant. |
CUSTOMER_BLOCKED | 409 | The customer is blocked by an operator. No verification is started and no payout is made for a blocked customer. |
VERIFICATION_DOCUMENT_GONE | 409 | ?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_INVALID | 400 | expected_date_of_birth is not a date this system will guess at. Send an ISO YYYY-MM-DD. |
IDENTITY_PROVIDER_SCHEMA_UNVERIFIED | 503 | Settlane 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_PERMITTED | 403 | This destination address is not permitted. Deliberately uninformative -- it never says which list answered. |
PAYOUT_ADDRESS_BLOCKED | 409 | The destination is on this tenant's own block list or the platform's global one. |
PAYOUT_ADDRESS_SANCTIONED | 403 | The destination is on a sanctions list. Do not retry and do not route around it. |
PAYOUT_ADDRESS_TETHER_FROZEN | 409 | Tether has frozen this address. Tokens sent to it cannot be moved again. |
PAYOUT_ADDRESS_CIRCLE_FROZEN | 409 | Circle has frozen this address. Tokens sent to it cannot be moved again. |
SANCTIONS_LIST_STALE | 503 | Settlane's sanctions list is older than its freshness window, so no payout is screened against it. Retry. |
TETHER_CHECK_UNAVAILABLE | 503 | The Tether freeze-list check could not run. Settlane's outage, not a refusal of the customer -- retry. |
CIRCLE_CHECK_UNAVAILABLE | 503 | The Circle freeze-list check could not run. Settlane's outage, not a refusal of the customer -- retry. |
PAYOUT_ADDRESS_COUNTERPARTY_RISK | 409 | A counterparty-risk provider refused this address. Not reachable today -- no such provider is integrated. |
COUNTERPARTY_RISK_CHECK_UNAVAILABLE | 503 | The counterparty-risk check could not answer, which is never read as clean. Settlane's outage -- retry. |
API_KEY_IP_NOT_ALLOWED | 403 | This 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.