Every failure has the same shape, whatever went wrong.
{
"error": {
"type": "card_error",
"code": "card_declined",
"message": "Insufficient funds.",
"decline_code": "insufficient_funds",
"doc_url": "https://ronda.sh/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. Internal detail — bank payloads, SQL, stack traces — never crosses this boundary.
| Type | Status | What it means |
|---|---|---|
invalid_request_error | 400 / 404 | Something about the request was wrong: a missing parameter, a bad value, an unknown object. |
authentication_error | 401 | The API key was missing, malformed, unknown or revoked. |
permission_error | 403 | Authenticated, but not allowed: a missing scope, a blocked IP, or a suspended account. |
card_error | 402 | The bank declined. Look at decline_code. |
rate_limit_error | 429 | Too many requests. Retry-After tells you when. |
idempotency_error | 409 | An Idempotency-Key was re-used with a different body, or is still in flight. |
api_error | 500 | Something failed on our side. Retry; the payment intent is already recorded. |
Normalised across banks, so your handling does not change when Ronda adds a second bank.
| Code | What to do |
|---|---|
card_declined | The issuer refused without a specific reason. Ask the customer to try another card. |
insufficient_funds | Not enough money on the card. The most common real decline — worth retrying later, which is what dunning does. |
expired_card | The card has expired. Only the customer can fix this; send them a portal link. |
incorrect_cvc | The security code did not match. |
authentication_required | 3-D Secure was required and not completed. |
binding_inactive | The saved card is no longer valid at the bank. Ask the customer to add a card again. |
limit_exceeded | The amount is above the card's limit. |
processing_error | The bank could not process the card. Safe to retry. |
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.
300 requests per minute per API key, and 120 per minute per IP on the unauthenticated surfaces. 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.