Developer documentationDocsGet API keys

Errors

Every failure has the same shape, whatever went wrong.

{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "message": "Insufficient funds.",
    "decline_code": "insufficient_funds",
    "payment": "pay_7f3a2c…",
    "doc_url": "https://ronda.am/docs/errors#card_declined"
  }
}

type tells you how to react; code is the stable machine-readable identifier; message is safe to show a human. param appears when a specific field was at fault, and a decline carries payment — the id of the failed payment, which you can fetch like any other. Internal detail — bank payloads, SQL, stack traces — never crosses this boundary.

Every response, success or failure, carries a ronda-request-id header. Quote it when you ask us about a request — it finds the exact line in our logs.

Error types

TypeStatusWhat it means
invalid_request_error400 / 404Something about the request was wrong: a missing parameter, a bad value, an unknown object.
authentication_error401The API key was missing, malformed, unknown or revoked.
permission_error403Authenticated, but not allowed: a missing scope, a blocked IP, or a suspended account.
card_error402The bank declined. Look at decline_code.
rate_limit_error429Too many requests. Retry-After tells you when.
idempotency_error409An Idempotency-Key was re-used with a different body, or is still in flight.
api_error500Something failed on our side. Retry; the payment intent is already recorded.

Error codes

CodeWhat to do
parameter_invalidA value was the wrong type, out of range or malformed. `param` names the field.
parameter_missingA required field was not sent. `param` names it.
parameter_unknownA field this endpoint does not take — usually a typo; the message suggests the name you probably meant. Unknown fields are refused rather than ignored, so a request never quietly does less than you wrote.
resource_missingNo object with that id exists for this key's merchant and mode. A test-mode id never resolves with a live key, or the other way round.
api_key_missingNo key was sent. Use `Authorization: Bearer sk_test_…`.
invalid_api_keyThe key is not one Ronda issued, or it has been rolled.
api_key_revokedThe key was revoked on the Developers page.
publishable_key_not_allowedA publishable key (pk_…) can only create checkout sessions. Use a secret key from your server.
missing_scopeA restricted key without the scope this endpoint needs.
ip_not_allowedThe key has an IP allowlist and this request came from elsewhere.
idempotency_key_in_useThe same Idempotency-Key was sent with a different body, or the first request is still running. Use a new key for a new operation.
amount_too_largeMore than one charge or price may be: ֏100 000 000, or 250 000.00 in USD/EUR. Usually a typo with extra zeros.
refund_exceeds_balanceThe refund is more than what is left on the payment after earlier refunds.
payment_not_refundableOnly a succeeded (or partly refunded) payment can be refunded.
plan_inactiveThe price is archived. Restore it, or use one that is still offered.
velocity_ip_blockedToo many card attempts from one address in a short time — Ronda's card-testing guard. Real customers rarely see it; wait and retry.
velocity_customer_blockedToo many card attempts for one customer in a short time. Wait and retry.
velocity_merchant_blockedLive mode only: too many card attempts, or too many declined ones, for this business in a short time, whatever address they came from. New card attempts pause for the window and Ronda is alerted. If it is real traffic, contact support to raise the ceiling.
coupon_currency_mismatchAn amount-off coupon is in one currency and the subscription is billed in another. Use a percentage coupon, or one in the subscription's currency.
test_mode_onlyOnly possible with a test key — simulating a renewal, for example.
refund_failedThe bank refused the refund. The message carries its reason; nothing was refunded.
dispute_closedThe chargeback is decided or past its evidence deadline, so evidence can no longer be added.
bank_not_connectedLive payments need a bank connected on the Settings page. Test mode works without one.
onboarding_incompleteThe account has not finished onboarding, so live payments are not available yet.
url_invalidA webhook URL that does not parse as a URL.
url_insecureA webhook URL that is not https.
url_port_blockedA webhook URL on a port other than 80 or 443.
url_credentialsA webhook URL with a username or password in it. Authenticate with the signature instead.
url_blockedA webhook host that can never be reached: localhost, *.internal, *.local.
url_unresolvableThe webhook's hostname does not resolve in DNS.
url_private_addressThe webhook's hostname resolves to a private, loopback or link-local address.
own_vpos_requiredLive payments need the business's own vPOS. Test mode works without one.
account_suspendedProcessing is suspended on this account, usually for an unpaid Ronda invoice. The dashboard's Billing page says why.
rate_limit_exceededToo many requests. Wait for the number of seconds in Retry-After.
request_too_largeThe request body is over the size limit.
internal_errorSomething failed on Ronda's side. Retrying with the same Idempotency-Key is safe.

Decline codes

Normalised across banks, so your handling does not change when Ronda adds a second bank.

CodeWhat to do
card_declinedThe issuer refused without a specific reason. Ask the customer to try another card.
insufficient_fundsNot enough money on the card. The most common real decline — worth retrying later, which is what dunning does.
expired_cardThe card has expired. Only the customer can fix this; send them a portal link.
incorrect_cvcThe security code did not match.
authentication_required3-D Secure was required and not completed.
binding_inactiveThe saved card is no longer valid at the bank. Ask the customer to add a card again.
limit_exceededThe amount is above the card's limit.
processing_errorThe bank could not process the card. Safe to retry.

Retrying safely

A 500 or a network timeout does not mean the payment failed — it means you do not know. Retry the request with the same Idempotency-Key: if the first attempt succeeded you get the original payment back, and if it did not, the charge happens exactly once. Never retry with a fresh key.

Rate limits

300 requests per minute per API key, and 120 per minute per IP on the unauthenticated surfaces. A publishable key also allows each visitor's IP 30 a minute, so one visitor cannot use up the key for everyone else. A 429 carries a Retry-After header in seconds. Card-testing velocity limits apply separately to checkout attempts: 10 per IP and 6 per customer in a ten-minute window, and in live mode 100 per business, or 20 declined ones, across every address.