Error Handling
The Genius Checkout API uses standard HTTP status codes. Error bodies are always JSON with a single error field.
Status codes
| HTTP Code | Meaning |
|---|---|
200 | Success |
201 | Created (refund, subscription, etc.) |
401 | Invalid or missing API key |
403 | Cross-mode operation — a gc_test_ key tried to touch live data, or vice versa |
404 | Resource not found |
409 | Idempotency conflict — see below |
422 | Validation error or payment declined |
429 | Rate limit exceeded — see Rate Limits |
500 | Server error — retry with backoff, contact support if persistent |
Error body
The standard error envelope is a single error string:
{ "error": "Human-readable error message" }For payment failures, the message includes the gateway decline reason where available.
Validation errors (422)
For controller-level validation (e.g. amount missing, currency unsupported, token not found, subscription token inactive), the body is the standard { "error": ... } envelope.
For Laravel's default field-level validation (the standard validate() path), the body is the framework's structured shape:
{
"message": "The given data was invalid.",
"errors": {
"amount": ["The amount field is required."],
"currency": ["The currency must be 3 characters."]
}
}Always check for both shapes. If errors is present, surface field-level messages to your UI; otherwise fall back to error.
Common 422 causes:
amountnot an integer or missingcurrencynot in the supported list (see Currencies)success_url/failure_urlnot absolute URLstax_amountdoesn't reconcile withsubtotalandamount- Token is not
active - A declined payment (see below)
Payment declines (422)
A declined payment returns 422 with a customer-safe message. The full gateway response (ISO code, AVS code, CVV result) is logged on the merchant dashboard for the merchant to inspect — never returned to the API caller, since some fields are not safe to expose. See Decline Codes for the buyer-facing message mapping.
Idempotency conflicts (409)
When you re-send a request with the same Idempotency-Key but a different body, the platform refuses to override the original — that's the whole point of idempotency. Two flavors of 409:
{ "error": "Idempotency key already used with different request parameters." }{ "error": "Request is still being processed. Please retry later." }The first means: the key has already been used for a different body — pick a new key, or send the original body. The second means: the original request is still in-flight (within a 30-second lock window) — wait briefly and retry. After the original completes, replays with the same body return the cached response.
Cross-mode (403)
gc_test_ keys and gc_live_ keys are strictly partitioned server-side. A test key cannot read, charge, refund, subscribe, or revoke a live token / transaction / customer (and vice versa). Mixed-mode requests return:
{ "error": "Cross-mode operation not permitted." }Picking the wrong key for the environment is the usual cause — verify your Authorization header.
Rate limits (429)
Default rate limits are generous and per-key. See Rate Limits for the full table, response headers, and backoff guidance.
Retrying
- 5xx and 429 are safe to retry. Use exponential backoff — 1s, 2s, 4s, 8s, etc.
- 4xx (other than 429) are not safe to retry as-is. Fix the request first.
- For mutating requests (create session, charge token, refund), include an
Idempotency-Keyheader. Replays return the original response — no duplicate charges. See Authentication → Idempotency.
