TillsafeDocstillsafe.comRequest early access

Error codes

Every error code the Tillsafe API returns, with its HTTP status, what it means and how to fix it.

Every error has the same shape, and its doc_url links to the code's entry on this page:

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "…",
    "param": "amount",
    "doc_url": "https://docs.tillsafe.com/api/errors#parameter_invalid",
    "request_id": "req_…"
  }
}
  • Branch on code. It is stable. message is written for people and can change: show it, never parse it.
  • param names the field the error is about, when there is one (amount, assets[1], Idempotency-Key).
  • type is the coarse class: invalid_request_error (400, 404, 405, 409, 413, 415), authentication_error (401), permission_error (403), idempotency_error (409), rate_limit_error (429) and api_error (500, 503).
  • request_id is also in the x-request-id header. Quote it when you contact support.
  • Retrying. A 5xx or 429 is safe to retry with the same Idempotency-Key. A 4xx fails the same way again until you change the request.

This list is checked against the API's source on every docs build: a code the API can return and this page does not describe fails the build.

Requests

body_invalid_json

400 The request body is not valid JSON. Fix: send a single JSON object, and check quoting and trailing commas. The message says where parsing stopped.

body_too_large

413 The request body is larger than the API accepts. Fix: send only the fields the endpoint takes; long metadata values and descriptions are the usual cause.

content_type_invalid

415 A body was sent without Content-Type: application/json. Fix: add the header.

method_not_allowed

405 The endpoint exists but not with this HTTP method (for example GET on a create endpoint). Fix: check the method in the API reference.

parameter_invalid

400 A parameter has a value the API does not accept: a malformed amount, an unknown network, a list that is too long, a bad cursor. param names it and the message says what is allowed. Fix: correct that field.

parameter_missing

400 A required parameter is missing, for example the amount of a fixed payment link. Fix: add the field named in param.

parameter_unknown

400 The body has a field the endpoint does not take. Unknown fields are refused rather than ignored, so a typo cannot silently drop an option. Fix: remove or rename the field named in param.

resource_missing

404 No object with this id exists in this mode, or it belongs to another account. If it exists in the other mode, the message says so: you used a test key for a live object or the reverse. 400 when an id inside the body (a subscription's customer or plan) does not exist. Fix: check the id and that the key's mode matches the object's.

route_not_found

404 No endpoint has this path. Fix: check the path against the API reference; every path starts with /v1/.

Authentication and keys

api_key_missing

401 The request has no API key. Fix: send Authorization: Bearer <key>.

api_key_invalid

401 The key is not one the API knows: mistyped, cut short, or rolled. Fix: copy the whole key (it starts with ts_test_ or ts_live_), or use the current one if it was rolled.

authorization_header_invalid

401 The Authorization header is not Bearer <key> or HTTP Basic with the key as the username. Fix: send Authorization: Bearer <key>.

secret_key_in_url

400 A secret key was sent in the URL. URLs end up in logs and browser history, so the request is refused. Fix: send the key in the Authorization header, and roll that secret key now: treat it as exposed.

publishable_key_not_allowed

403 The endpoint needs a secret key; a publishable key only reaches the checkout endpoints. Fix: call it from your server with your secret key.

api_key_permission_denied

403 The key cannot do this because the team member who created it may not. Fix: use a key created by an owner or admin.

livemode_disabled

409 A live key was sent to a server that runs in test mode only. Fix: use a ts_test_ key there.

mode_not_configured

409 Your account is not set up in this key's mode yet (typically live mode before it is enabled). Fix: use a key of the mode your account has, or ask for live mode to be enabled.

test_mode_only

403 A test helper such as POST /v1/test_helpers/invoices/{id}/simulate_payment was called with a live key. Live mode has no simulated payments. (409 from the local in-memory demo server.) Fix: use a test key. See testing.

Idempotency

idempotency_key_required

400 A POST came without an Idempotency-Key header. Fix: send one unique value per logical operation, such as your order id or a UUID. Retrying with the same key is always safe.

idempotency_key_invalid

400 The key is empty, too long or not visible ASCII. Fix: use 1 to 255 visible ASCII characters.

idempotency_key_reused

409 The key was already used for a different request (another path or body). Fix: use a new key for a new operation. To retry the original, resend exactly the same request.

idempotency_key_in_use

409 A request with this key is still being processed. Fix: retry shortly with the same key; you get the first request's result.

Invoices

currency_unsupported

400 The currency is unknown, or the invoice's assets cannot be priced in it: stablecoins are priced only in their own currency, so a EUR invoice cannot offer USDT. Fix: price stablecoin invoices in USD, or list only assets that can be priced in your currency.

asset_unavailable

400 An asset in assets (or your default_assets) is not available to your account in this mode: unknown, a mainnet asset with a test key or the reverse (the message says which), or on a chain you have no payout address for. The message lists what is available. Fix: use one of the listed asset codes. See payment options.

price_unavailable

409 A volatile asset (BTC) cannot be priced right now because no current price is available. Fix: retry shortly, or offer a stablecoin.

deposit_addresses_exhausted

409 No deposit address can be issued on a network: every address in your pool is leased or cooling down, or, for Bitcoin, your wallet's gap limit is reached. Fix: retry shortly; add addresses to your pool, or raise your wallet's gap limit. See deposit addresses.

invoice_not_awaiting_payment

409 The invoice no longer takes payments or wallet connections: it is paid, expired, cancelled or under review. Fix: read the invoice's status; create a new invoice if the payer still needs to pay.

invoice_not_cancellable

409 The invoice cannot be cancelled because a payment has already been seen for it (or it is already closed). Fix: let it complete, then refund it if you need to.

review_not_open

409 POST /v1/invoices/{id}/resolve_review was called on an invoice that is not under_review. Fix: read the invoice's status; only a held payment can be accepted or rejected.

review_requires_dashboard

409 In live mode an API key tried to accept a risk hold (sanctions, a blacklist match or a finality violation). Fix: a person accepts it on the dashboard; an API key can only reject it. See reviews.

Checkout and wallet payments

These come from the payer-facing checkout endpoints (publishable key).

asset_not_offered

400 The asset is not one of this invoice's payment options. Fix: use an asset from the invoice's payment_options.

wallet_unsupported

400 A wallet connection was requested for a network without wallet payments. Wallet payments exist on TRON and EVM networks only. Fix: pay by address and QR code.

sender_invalid

400 The wallet address sent as sender is not a valid address on that network. Fix: connect the wallet again.

wallet_binding_limit

409 The invoice already has the most connected wallets allowed on this network (5). Fix: pay from one of the wallets already connected, or by address and QR code.

unique_amount_unavailable

409 This wallet has too many open payments of the same amount to the same payout address, so no unique amount is left. Fix: pay one of them first, or pay by address and QR code.

checkout_session_invalid

400 The session_token is not a checkout session of this invoice. Fix: send the session_token from GET /v1/checkout/invoices/{id}; reloading the payment page gets a fresh one.

email_invalid

400 The email address for a payer receipt does not look like an email address. Fix: correct it, or leave it out: the email is optional.

notify_limit_reached

409 This payment already has the most email addresses it can take. Fix: use one of the addresses already given.

notify_session_mismatch

409 An email address for this payment was already left from another browser or tab. Fix: change it there.

feature_unavailable

409 This server does not offer the feature, such as payer email notifications. Fix: check the checkout invoice's capabilities before offering it.

Refunds

amount_invalid

400 The amount is not a valid amount of the asset, or is zero. Fix: send a positive decimal string with no more decimals than the token has.

amount_required

400 A refund without an amount defaults to the overpayment, and this invoice was not overpaid. Fix: say how much to refund in amount.

asset_not_received

400 The refund's asset is not one this invoice received (or not an asset code the server knows). Fix: use an asset the invoice actually received.

nothing_received

409 Nothing has been received for this invoice in that asset, so there is nothing to refund. Fix: check the invoice's payments.

refund_exceeds_received

409 The amount is more than can still be refunded: what was received, minus refunds already open or sent. The message says the most you can refund. Fix: lower the amount.

refund_not_ready

409 POST /v1/refunds/{id}/mark_sent was called on a refund that is not ready_to_send: the payer has not claimed it yet, or its address is in review. Fix: wait for refund.claimed; never send a refund in review.

refund_already_sent

409 The refund was already sent, so it cannot be cancelled. Fix: nothing to undo; the refund is final.

network_unavailable

400 The payer chose a network this refund cannot be received on. The message lists the networks it can. Fix: choose one of those.

claim_expired

409 The refund link expired (30 days) before the payer claimed it. Fix: create a new refund and send its link.

claim_cancelled

409 You cancelled this refund, so its link no longer works. Fix: create a new refund if one is still owed.

claim_already_sent

409 The refund has already been sent. Fix: nothing to do.

claim_already_submitted

409 The payer already gave a payout address for this refund, and a different one cannot replace it. Fix: cancel the refund and create a new one if the address was wrong.

amount_too_small

400 The amount the payer chose is below the link's min_amount. Fix: enter at least the minimum the message names.

amount_too_large

400 The amount the payer chose is above the link's max_amount. Fix: enter at most the maximum the message names.

409 The link was switched off (active: false). Fix: switch it on again, or share a new link.

409 The link's expires_at has passed. Fix: clear or move expires_at, or share a new link.

409 quantity_used reached quantity_limit. Fix: raise the limit if you can sell more.

409 Every remaining unit is reserved by a payer who has not paid yet. A reservation that is not paid is released when its invoice expires. Fix: try again later.

409 Too many unpaid invoices are open for the link (50) or across your links (200). Fix: try again in a few minutes. See payment links.

Subscriptions

plan_archived

409 The plan is archived and takes no new subscriptions. Fix: subscribe the customer to an active plan.

subscription_state

409 The subscription's status does not allow this action, for example resuming a cancelled subscription or cancelling at the period end before anything was paid. Fix: read its status; the message says what is possible.

subscription_invoice_failed

409 The subscription's invoice could not be created; the message says why (often an asset that is not available). Fix: correct the plan's assets or your account's defaults.

Settings and webhooks

destination_immutable

403 registered_destinations was sent to POST /v1/merchant/settings. Payout addresses cannot be changed with an API key. Fix: change them on the dashboard. See the security model.

live_change_requires_dashboard

403 In live mode an API key tried to change the webhook URL or roll the webhook secret. Fix: make the change on the dashboard (passkey confirmation, a delay, notice to every owner and admin). API keys can do it in test mode.

webhook_url_forbidden

400 The webhook URL is not allowed: not https://, carries credentials, or points at a private or local address. Fix: use a public https:// URL. See webhooks and signatures.

webhook_url_missing

409 A delivery was replayed, but no webhook_url is set. Fix: set one with POST /v1/merchant/settings first.

Rate limits and server errors

rate_limited

429 Too many requests. Fix: wait for the number of seconds in the Retry-After header, then retry with the same Idempotency-Key.

internal_error

500 Something went wrong on Tillsafe's side. Fix: retry with the same Idempotency-Key; if it persists, contact support with the request_id.

service_unavailable

503 The service is temporarily unavailable. Fix: retry with the same Idempotency-Key, with backoff.

Dashboard

These come from the dashboard's own endpoints (signed-in sessions, not API keys). The dashboard is in early access and not public.

account_disabled

403 This account is disabled. Fix: ask an owner of your organization.

address_invalid

400 The address is not a valid address on the chosen network (or a refund claim's address is invalid). Fix: check the address and network.

already_member

409 The person invited is already on the team.

application_approved

409 The live-mode application is already approved.

application_pending

409 The live-mode application is already waiting for review. You can change it if more information is asked for.

application_not_approved

409 Only an approved application can go live.

application_not_reviewable

409 The application is in a state that cannot be reviewed now.

cannot_change_self

403 You cannot change your own role or remove yourself. Fix: ask another owner.

ceremony_expired

401 The passkey prompt expired or was already used. Fix: try again.

change_pending

409 A protected change of this kind (a payout address or webhook setting) is already pending. Fix: cancel it first to request another.

change_not_pending

409 The protected change is no longer pending: it was already applied or cancelled.

csrf_failed

403 The request is missing its security token. Fix: reload the page and try again.

destination_invalid

400 A payout destination given during onboarding is not valid. Fix: check the addresses and networks.

destinations_not_ready

409 The application's payout destinations are missing or still inside their protection delay.

invite_invalid

401 The invitation link is invalid, already used, revoked or expired. Fix: ask for a new one.

last_owner

409 An organization needs at least one owner. Fix: make someone else an owner first.

401 The sign-in link is invalid, expired or already used. Fix: request a new one.

mfa_not_pending

409 The session is already fully signed in.

network_invalid

400 The network is unknown or not a supported mainnet for this step.

network_not_enabled

400 The network is not one of your payout networks.

no_change

400 The value given is already the current one.

no_organization

403 You are not a member of an organization. Fix: ask an owner to invite you.

operator_conflict

403 An operator cannot review the application of an organization they belong to.

origin_not_allowed

403 The request did not come from the dashboard's own origin.

passkey_already_registered

409 This passkey is already registered.

passkey_failed

401 The passkey could not be verified. Fix: try again, or use another sign-in method.

passkey_required

409 Protected actions are confirmed with a passkey, and the account has none. Fix: add a passkey first.

permission_denied

403 Your role does not allow this (or an API key would get more than your role allows). Fix: ask an owner or admin.

role_not_assignable

403 Your role cannot grant, change or revoke that role. Fix: ask an owner.

second_factor_required

403 Sign-in is not finished. Fix: enter the code from your authenticator app.

session_expired

401 The session has ended. Fix: sign in again.

session_missing

401 There is no session. Fix: sign in.

401 The sign-up link is invalid, expired or already used. Fix: start again to get a new one.

step_up_required

403 This action needs a fresh confirmation. Fix: confirm with your passkey, then try again.

too_many_attempts

429 Too many sign-in or confirmation attempts. Fix: wait for Retry-After, then try again.

too_many_signups

429 Too many sign-ups from this network. Fix: wait for Retry-After, then try again.

totp_already_enabled

409 An authenticator app is already set up. Fix: remove it first to set up another.

totp_not_set_up

409 No authenticator app is set up, or none is waiting for this code.

Local demo server

The in-memory demo server (serve --in-memory) simulates one payment per invoice and returns two codes of its own. The hosted API never does.

already_paid

409 The demo already simulated a payment for this invoice. Fix: create a new invoice.

invoice_cancelled

409 The invoice is cancelled, and the demo does not simulate late payments. Fix: test late payments against the hosted test mode.