Error Codes
API errors are returned with a consistent standard error response. Raw bank error codes (bank transaction/result codes) are not surfaced to the customer; you only see the stable codes below.
Standard error response
Section titled “Standard error response”{ "error": { "code": "terminal_not_allowed", "message": "Terminal is not allowed for this partner.", "traceId": "trc_9f12ab" }}| Field | Description |
|---|---|
code |
Stable, machine-readable error code (catalog below). |
message |
Human-readable description. |
traceId |
A trace identifier to share in support requests. |
HTTP status mapping
Section titled “HTTP status mapping”- Validation and business-rule violations →
4xx(e.g.,400,409,422). - Authentication failure →
401; authorization or access denial →403. - The requested resource was not found or is outside your access scope →
404. - Rate limit exceeded →
429(the response may carry aRetry-Afterheader telling you how long to wait). - The platform is temporarily at its capacity ceiling →
503(reserved — not currently produced; see the code catalog below for details and the429distinction). - Every error response carries an
error.codeand atraceId.
Code catalog
Section titled “Code catalog”| Code | HTTP | Meaning |
|---|---|---|
validation_error |
400 |
The request body failed validation (a missing or invalid field). |
sensitive_data_not_allowed |
400 |
A free-text field (for example, a payment link’s items[].name/items[].note/payerName) has a value where, after removing spaces and hyphens, the entire field matches a card-number or IBAN pattern. The check only triggers when the whole field matches; a card number or IBAN embedded inside other text is not rejected. |
duplicate_partner_tx_code |
409 |
The same partnerTxCode was used with a different request (or belongs to a payment link) — the code is in use for a different charge. The same partnerTxCode with the same request does not produce this error; it returns a 200 idempotent replay. See Idempotency: partnerTxCode. |
terminal_not_allowed |
403 |
The terminal does not belong to the partner or is inactive. |
partner_not_active |
403 |
The partner account is not active (suspended or closed); new payments and partner-initiated refunds are blocked. |
amount_limit_exceeded |
422 |
A per-transaction min/max amount limit is configured for your account and the amount is outside that range. Without a configured limit (the default), only positivity is validated: a negative or 0 amount is rejected with 400 validation_error. |
pos_per_tx_limit_exceeded |
422 |
A per-transaction limit configured on the bank connection behind your terminal was exceeded. This differs from amount_limit_exceeded: that one is your own account’s limit; this one is a limit on your bank connection. See bank connection limits. |
pos_daily_limit_exceeded |
422 |
A daily limit configured on the bank connection behind your terminal is full. See bank connection limits. |
pos_weekly_limit_exceeded |
422 |
A weekly limit configured on the bank connection behind your terminal is full. See bank connection limits. |
pos_monthly_limit_exceeded |
422 |
A monthly limit configured on the bank connection behind your terminal is full. See bank connection limits. |
pos_time_window_limit_exceeded |
422 |
A limit configured for a specific time window on the bank connection behind your terminal is full. See bank connection limits. |
installment_not_allowed |
422 |
The card is not eligible for installments, the terminal has no installment permission, the card’s brand is known but this brand × installment count is not offered at your terminal, or a payment link’s installment restriction was violated. See the remedy under installment mismatch. |
payment_not_found |
404 |
The requested payment was not found or does not belong to this partner. |
payment_not_refundable |
422 |
The payment is not in a refundable state. |
refund_amount_exceeds_remaining |
422 |
The requested refund amount exceeds the remaining refundable balance. |
refund_not_yet_available |
422 |
On the default refund endpoint (POST .../refunds), a same-processing-day partial refund: available after day-end. |
refund_method_not_available |
422 |
On the POST .../refunds/void endpoint, the forced method cannot be applied to this payment: the requested amount does not equal the entire remaining balance, or the payment already has an approved credit refund. |
refund_in_progress |
409 |
A pending refund already exists for this payment (single-flight), or this partnerRefundCode was already used (duplicate code). |
refund_not_found |
404 |
The requested refund was not found or does not belong to this partner. |
direct_refund_not_allowed |
422 |
This payment was made on a direct-collection (direct) terminal; a credit refund is not possible — only a same-day full void is available. |
payment_link_not_found |
404 |
The requested payment link was not found or does not belong to this partner. |
payment_link_already_paid |
409 |
The payment link is already completed. (Rarely, the same error is returned if your cancellation races with a payment completing at that exact moment.) |
payment_link_expired |
409 |
The payment link’s window has expired; it can no longer be cancelled. |
payment_link_cancelled |
409 |
The payment link was already cancelled. |
payment_link_items_amount_mismatch |
422 |
The items you sent when creating a payment link do not add up to the amount field. Partial itemization is not supported. |
rate_limited |
429 |
Too many requests: the rate limit was exceeded. Wait a while and retry. |
service_overloaded |
503 |
The platform is temporarily at its capacity ceiling; your request was rejected in a controlled way. Not currently produced (reserved — the capacity control runs in observation mode today; this behavior applies once it is activated). When active: the response carries a Retry-After header (in seconds); wait that long and retry with exponential backoff. ⚠ Don’t confuse this with rate_limited (429): rate_limited means a client quota for that endpoint was exceeded on an API-key, account, or source-IP axis; service_overloaded is the platform’s overall capacity signal. |
Installment mismatch (installment_not_allowed)
Section titled “Installment mismatch (installment_not_allowed)”installment_not_allowed can occur for four distinct reasons:
- The card is not eligible for installments (installment sales are supported only for certain cards).
- Your terminal’s effective installment set (which may be narrowed on your account; see Installment Options) does not cover this installment count.
- The card’s brand is known, but this brand × installment count is not offered at your terminal. Installment Options tells you exactly which installment counts are offered: every count missing from that list produces this error.
- The
allowedInstallmentCountsyou sent when creating a payment link is empty, or is not a subset of your terminal’s effective installment set.
When it occurs: reason 2 does not depend on the card’s brand, so it is evaluated in the POST /v1/payments call itself, at create time. Reasons 1 and 3 require the card’s brand, so they only surface during the Card Handoff’s 3D relay. Reason 4 is evaluated at create time on POST /v1/payment-links.
The remedy is the same for reasons 1-3: the installment count is fixed when you create the payment (POST /v1/payments) and cannot be changed on the same payment. If you get this error, create a new cardless payment with a different installment count; the same payment can only be retried with a different card — the installment count stays fixed. ⚠ Note: reasons 1 and 3 surface during the 3D relay, so each attempt draws down this payment’s card attempt allowance; once it runs out the payment is locked to failed (card_attempt_limit_exceeded) and the paymentRef can no longer be used. In other words, “retry with a different card” is not unlimited. See Payment Flow — card attempt limit. To see this coming ahead of time, query which installment counts and commission rates the card qualifies for on your checkout using Installment Options. For reason 4, the remedy is to resend allowedInstallmentCounts as a subset of your terminal’s effective installment set (this restriction cannot be changed once the link is created).
Bank connection limits
Section titled “Bank connection limits”Your bank may configure separate per-transaction, daily, weekly, monthly, and time-window limits on the bank connection behind your terminal. When one of these limits is full, the request is rejected with 422; you learn which one from the specific code returned. Threshold or current-usage values never appear in any response.
| Code | Window |
|---|---|
pos_per_tx_limit_exceeded |
Per transaction |
pos_daily_limit_exceeded |
Daily |
pos_weekly_limit_exceeded |
Weekly |
pos_monthly_limit_exceeded |
Monthly |
pos_time_window_limit_exceeded |
A specific time window |
When it occurs: this can surface at two different moments, and the behavior differs:
- At payment creation (
POST /v1/payments): the request is rejected with422directly to your server, with the specific code inerror.code. The payment is not created;partnerTxCodeis not consumed, so you can retry the same request later. - At the start of 3D (during Card Handoff): the rejection happens in the buyer’s browser/WebView. The buyer sees a generic, sterile error page — no code-specific message is shown, and
error.codeis not returned to your server. The payment’s status does not change (it staysinitiated) and this attempt does not draw down the card attempt allowance; you can retry with the samepaymentRef. This also applies to payments started via a payment link.
If you get this error, wait a while and retry, or contact your business’s account manager.
Handoff errors
Section titled “Handoff errors”The codes below occur during Card Handoff. For payment_ref_invalid / payment_ref_expired / payment_prepare_already_started / card_attempt_limit_exceeded, the remedy is the same: create a new cardless payment (POST /v1/payments), then start the handoff again with the new paymentRef/handoffUrl it returns. virtual_pos_inactive and psp_inactive are different: your terminal’s bank connection (or the payment provider behind it) is temporarily inactive, so creating a new payment reproduces the same error — wait until it is resolved or contact support. virtual_pos_card_type_not_supported is different too, and it depends on the card: creating a new payment with the same card reproduces the same error — ask the buyer for another card. payment_declined also depends on the card, but needs no new payment: the payment stays initiated, so the buyer can continue on the same payment with a different card.
| Code | HTTP | Meaning |
|---|---|---|
payment_ref_invalid |
404 |
The paymentRef is invalid/unknown. |
payment_ref_expired |
404 |
The paymentRef has expired. |
payment_prepare_already_started |
409 |
3D has already been started for this paymentRef (single-use). |
card_attempt_limit_exceeded |
429 |
The allowed number of card attempts for this paymentRef was exceeded; the payment is locked to failed. In your retry logic, treat this as a trigger to create a new cardless payment, not to retry with a different card on the same payment. |
virtual_pos_inactive |
422 |
The bank connection backing this terminal is currently inactive; a new payment cannot be prepared. In-flight (already started) transactions are unaffected. |
psp_inactive |
422 |
The payment provider behind this terminal’s bank connection is currently inactive; a new payment cannot be prepared. In-flight (already started) transactions are unaffected. The payment stays initiated and the attempt does not draw down the card attempt allowance. |
virtual_pos_card_type_not_supported |
422 |
The bank connection backing this terminal does not support this card type; the payment cannot be prepared. In practice you see this most often with foreign-issued cards. The payment stays initiated, but the attempt draws down the card attempt allowance. To see it coming: Installment Options — options comes back empty for an unsupported card type. |
payment_declined |
422 |
The bank declined the card while 3D verification was being started (a card-level reason: e.g., an invalid card, or a transaction on a closed card). The payment’s status does not change (it stays initiated); the buyer can try a different card on the same payment. The attempt draws down the card attempt allowance; once it runs out the payment is locked to failed (card_attempt_limit_exceeded). |