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:
{
"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.messageis written for people and can change: show it, never parse it. paramnames the field the error is about, when there is one (amount,assets[1],Idempotency-Key).typeis 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) andapi_error(500, 503).request_idis also in thex-request-idheader. Quote it when you contact support.- Retrying. A
5xxor429is safe to retry with the sameIdempotency-Key. A4xxfails 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.
Payment links
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.
payment_link_inactive
409 The link was switched off (active: false). Fix: switch it on again, or share a new link.
payment_link_expired
409 The link's expires_at has passed. Fix: clear or move expires_at, or share a new link.
payment_link_sold_out
409 quantity_used reached quantity_limit. Fix: raise the limit if you can sell more.
payment_link_fully_reserved
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.
payment_link_busy
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.
magic_link_invalid
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.
signup_link_invalid
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.