Skip to content
Sandbox open: magic test cards, hosted checkout and signed webhooks · 55 payment methods in the catalogue, cards to crypto · Built and hosted in the EEA · Read the API reference at /developers

Sandbox open: magic test cards, hosted checkout and signed webhooks

Documentation

API reference

Errors

The error envelope, every error type with its status, the codes you will actually see, and which decline codes are safe to retry.

Updated

Every failure — validation, authentication, a declined card, a rate limit, our own fault — comes back as the same object, with the same shape, at the HTTP status its type implies.

{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "decline_code": "insufficient_funds",
    "message": "The payment was declined (insufficient_funds, soft — the customer may retry). Payment pay_01JBXQ2M4K7T9V3ZC8HR5N6PWD is recorded as declined.",
    "param": "payment_method",
    "request_id": "req_01JBXQ2M4K7T9V3ZC8HR5N6PWD"
  }
}

type is the stable thing to branch on. code is stable too but the set grows; treat an unknown code as its type. message is written for a human reading a log and may change without a version bump — never match on it. param names the offending field, dotted for nested ones (payment_method.token). request_id is also on every response as the X-Request-Id header; quote it when you ask about a request.

Types

HTTPtypeWhen
400validation_errorMalformed or missing parameters, or a body that is not valid JSON.
401authentication_errorMissing, malformed, unknown, revoked or expired key.
403authorization_errorThe key is valid but may not do this — wrong key type, wrong environment.
402card_errorThe issuer declined. decline_code is populated.
404not_found_errorThe resource does not exist, or belongs to another merchant. We never say which.
409idempotency_errorAn Idempotency-Key reused with different parameters, or still in flight.
422processing_errorThe request is valid and cannot be processed — unroutable method, limit exceeded, wrong state.
429rate_limit_errorToo many requests. Retry-After says when.
5xxapi_errorOur side. Retry with the same Idempotency-Key.

404 is deliberately indistinguishable from "belongs to someone else": disclosing that an id exists elsewhere would leak another merchant's activity.

Codes you will see

Validation (400):

codeMeaning
invalid_parameterThe default for a schema failure. param and message name the field.
invalid_jsonThe body did not parse.
raw_card_data_not_allowedA card number, expiry or security code appeared in a body. See below.
token_invalidThe tok_ is unknown, already used, or expired.
invalid_card_numberTokenizer only: not a valid test number.
card_expiredTokenizer only: the expiry is in the past.
amount_exceeds_refundableThe refund is larger than what is left to refund.
invalid_url, https_required, private_address, unresolvable_hostA webhook URL that failed the SSRF guard.

Processing (422):

codeMeaning
payment_failedThe processor could not process the authorisation. The payment is failed.
capture_failedThe capture was refused. The payment stays authorized.
void_failedThe void was refused. The payment keeps its status.
payment_not_capturableThe payment is not in a state that can be captured.
payment_not_refundableThe payment has not been captured, or is fully refunded.
payment_not_cancelableThe payment is already captured or terminal.
payment_not_confirmableThe payment is not pending.
payment_not_advanceableSandbox only: the payment is not processing.
no_routeNo connector is configured for that method and currency on this account.
amount_above_limitAbove the per-transaction limit configured for the account.
endpoint_limit_reachedAlready 16 enabled webhook endpoints in this environment.
no_matching_endpointNothing is subscribed to the event you asked to resend.

Idempotency (409): idempotency_key_conflict and idempotency_key_in_use. See idempotency.

Rate limits (429): rate_limit_exceeded, with Retry-After.

Ours (5xx): internal_error, and connector_http_error when the processor itself answered with a server error. Both are safe to retry with the same Idempotency-Key.

Never send raw card data

Any body containing something that looks like a card number, an expiry pair or a security code is rejected before the schema runs, on every endpoint except the sandbox tokenizer:

{
  "error": {
    "type": "validation_error",
    "code": "raw_card_data_not_allowed",
    "message": "Raw card data is not accepted here. Tokenise the card with hosted fields (or POST /v1/sandbox/tokens with a test key) and send payment_method.token. Accepting card numbers on this endpoint would put your integration in PCI DSS scope.",
    "param": "payment_method.number",
    "request_id": "req_01JBXQ2M4K7T9V3ZC8HR5N6PWD"
  }
}

This is not a formality. It is what keeps a card number off your servers and your SAQ A scope intact, and the guard runs on the raw body so a schema that would have stripped the field cannot let it through.

Declines

A decline is a 402 and the payment still exists. Read it back with GET /v1/payments/:id whenever you want the full outcome: outcome.decline_code, outcome.decline_is_soft, outcome.response_code, plus the AVS, CVV, 3-D Secure and risk fields.

decline_is_soft is the only field you should base a retry on.

decline_codeSoft?What it means, and what to do
insufficient_fundssoftNo funds right now. A later retry can succeed.
issuer_unavailablesoftThe issuer did not answer. Retry with backoff.
try_again_latersoftA transient issuer refusal. Retry with backoff.
sca_requiredsoftAuthentication is needed. Retry with a fresh challenge, not with the same attempt.
do_not_honorhardThe issuer refused without a reason. Ask for another method.
generic_declinehardAs above, with even less information.
expired_cardhardCollect a new expiry.
incorrect_cvchardCollect the security code again. One retry, not a loop.
invalid_accounthardThe account does not exist. Ask for another method.
lost_cardhardDo not retry and do not tell the cardholder why.
stolen_cardhardAs above.
restricted_cardhardThe card may not be used for this. Ask for another method.
transaction_not_allowedhardThe issuer does not permit this transaction type.
fraud_suspectedhardDo not retry.

Retrying a hard decline does not help and does count: repeated attempts on the same card are what issuers and schemes read as card testing, and the cost lands on your approval rate.

Show a payer "that card was declined — try another card or another method". Never show them the decline code. lost_card and stolen_card in particular exist for your logs, not for the person at the checkout.