Errors
Error envelope, status codes, and retry guidance for the RevKeen API
The RevKeen API returns errors in a consistent envelope with a machine-readable code, a human-readable message, and — where applicable — the offending param and structured field-level details.
Reserve the message for display and the code for branching logic.
Non-2xx status codes always return an error object. Inspect error.code before error.message when writing retry or recovery logic.
Error envelope
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "Customer email is required.",
"param": "customer.email",
"details": {
"fields": {
"customer.email": ["must be a valid email address"]
}
}
}
}| Field | Type | Present on | Purpose |
|---|---|---|---|
error.type | string (enum) | All errors | Broad category: invalid_request_error, authentication_error, permission_error, etc. |
error.code | string (enum) | All errors | Stable machine-readable identifier. Safe for logic branching. |
error.message | string | All errors | Human-readable description. Content may change; do not parse. |
error.param | string | Validation errors | Dotted path of the offending parameter. |
error.details | object | Validation errors | Structured extras — most commonly details.fields[param] = [reasons]. |
HTTP status codes
| Status | Meaning | When you see it |
|---|---|---|
400 | Invalid request | Malformed JSON, unknown fields, failed validation. Do not retry without fixing the payload. |
401 | Unauthenticated | Missing, malformed, or revoked API key. |
403 | Permission denied | Key is valid but lacks the required scope, or the resource belongs to a different merchant. |
404 | Not found | Resource does not exist, or is not visible to your API key. |
409 | Conflict | Idempotency-Key collision with a different body, or a concurrent state-transition conflict. |
422 | Unprocessable entity | Business-rule failure (for example, refunding more than the original charge). |
429 | Rate limit exceeded | Back off using the Retry-After header. |
500 | Server error | Transient — retry with exponential backoff. |
503 | Service unavailable | Capacity issue — retry with exponential backoff. |
Retry guidance
| Status / Condition | Retryable? | Strategy |
|---|---|---|
500, 502, 503, 504 | Yes | Exponential backoff, at least 3 attempts, capped at ~30s total. |
429 | Yes | Honour the Retry-After header (seconds). Do not retry sooner. |
| Network timeout on a mutation | Yes | Retry with the same Idempotency-Key — see Idempotency. |
400, 404, 422 | No | Permanent — fix the payload or resource first. |
401, 403 | No | Rotate or rescope the API key, then retry. |
409 (idempotency conflict) | No | The key was reused with a different body. Generate a new key. |
Validation errors
Validation errors (type: invalid_request_error, code: validation_error) always include a details.fields map. Keys are dotted parameter paths; values are arrays of reasons.
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "One or more fields are invalid.",
"details": {
"fields": {
"items[0].quantity": ["must be greater than 0"],
"customer.email": ["must be a valid email address"]
}
}
}
}Render field errors next to the offending input rather than surfacing message alone.
Rate-limit errors
429 Too Many Requests responses always include a Retry-After header in seconds:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{ "error": { "type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Too many requests." } }Published plan limits are on the rate limits reference.
Examples
curl -i https://staging-api.revkeen.com/v2/customers/cus_does_not_exist \
-H "x-api-key: $REVKEEN_API_KEY"
# HTTP/1.1 404 Not Found
# { "error": { "type": "invalid_request_error", "code": "resource_missing", "message": "Customer not found." } }import { RevKeen, RevKeenError } from "@revkeen/sdk";
const client = new RevKeen({ apiKey: process.env.REVKEEN_API_KEY! });
try {
await client.customers.retrieve("cus_does_not_exist");
} catch (err) {
if (err instanceof RevKeenError) {
if (err.code === "resource_missing") {
// permanent — do not retry
}
}
throw err;
}import revkeen "github.com/RevKeen/sdk-go"
_, err := client.Customers.Retrieve(ctx, "cus_does_not_exist")
if rkErr, ok := revkeen.AsError(err); ok {
if rkErr.Code == "resource_missing" {
// permanent — do not retry
}
}use RevKeen\RevKeenClient;
use RevKeen\Exception\RevKeenException;
$client = new RevKeenClient(['api_key' => getenv('REVKEEN_API_KEY')]);
try {
$client->customers->retrieve('cus_does_not_exist');
} catch (RevKeenException $e) {
if ($e->getCode() === 'resource_missing') {
// permanent — do not retry
}
throw $e;
}