code.
Error shape
{
"error": {
"type": "validation_error",
"code": "email_invalid",
"message": "The provided email address is not valid.",
"param": "email",
"requestId": "req_01HX3ZE..."
}
}
| Field | Description |
|---|---|
type | Broad category. One of authentication_error, permission_error, validation_error, not_found, conflict, rate_limit_error, api_error. |
code | Stable, machine-readable identifier. Switch on this in your code. |
message | Human-readable summary. Safe to surface in logs; do not show to end users without localization. |
param | Field name that caused the error (when applicable). |
requestId | Echoed from the x-request-id response header. Include this when filing a support ticket. |
HTTP status codes
| Code | Meaning | Retry? |
|---|---|---|
200 / 201 | Success. | - |
400 | Bad request. validation_error in the body. | No - fix the request. |
401 | Authentication failed. Missing, invalid, or revoked key. | No. |
403 | Authenticated, but the key lacks permission. | No. |
404 | Resource not found, or not visible to this tenant. | No. |
409 | Conflict. Duplicate resource, or illegal state transition. | No. |
429 | Rate limited. | Yes, with exponential backoff - honor Retry-After. |
500 | Zquence had a problem. | Yes, with backoff. |
503 | Temporary outage. | Yes, with backoff. |
Common error codes
Authentication
| Code | When |
|---|---|
key_missing | No x-api-key / x-api-secret header. |
key_invalid | Key pair does not match any active key. |
key_revoked | Key has been revoked. |
signature_invalid | (Webhook handler) zquence-signature did not match. |
Validation
| Code | When |
|---|---|
email_invalid | Email failed RFC 5322 validation. |
field_required | Required field missing - param names the field. |
field_too_long | param exceeded its max length. |
enum_invalid | param is not one of the allowed values. |
Resource
| Code | When |
|---|---|
resource_not_found | The object doesn’t exist, or belongs to a different tenant. |
duplicate_email | Trying to create a user with an email that already exists for this tenant. |
kyc_already_completed | KYC record has been finalized and can’t be resubmitted. |
transaction_not_pending | Transaction is no longer in a state that allows the requested action. |
Rate limits
| Code | When |
|---|---|
rate_limit_exceeded | You’ve exceeded the per-key rate limit. Retry after Retry-After seconds. |
Handling errors idiomatically
const res = await fetch(`${BASE}/tenants/add-user`, {
method: "POST",
headers: authHeaders,
body: JSON.stringify({ name: "Ada", email: "ada@example.com", phone: "+14155552671" }),
});
if (!res.ok) {
const { error } = await res.json();
if (error.code === "duplicate_email") {
// Merge with the existing user
} else if (error.type === "rate_limit_error") {
const wait = Number(res.headers.get("retry-after") ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
// retry
} else {
logger.error({ requestId: error.requestId }, error.message);
throw new Error(error.message);
}
}
res = requests.post(f"{BASE}/tenants/add-user", headers=auth_headers,
json={"name": "Ada", "email": "ada@example.com", "phone": "+14155552671"})
if not res.ok:
err = res.json()["error"]
if err["code"] == "duplicate_email":
...
elif err["type"] == "rate_limit_error":
time.sleep(int(res.headers.get("Retry-After", 1)))
else:
raise RuntimeError(err["message"])
Always log
requestId. When you email support, we can pull the exact request, response, and timing from a single ID.