Errors
Ching uses standard HTTP status codes and wraps every response in a success envelope. Non-2xx responses always include a machine-readable error code you can branch on.
Response Envelope
Every response has the same shape:
{
"success": true,
"data": {
"id": "cus_..."
}
}{
"success": false,
"error": {
"status": 400,
"code": "INVALID_FIELD",
"message": "Field validation failed",
"issues": [
{
"path": [
"amount"
],
"message": "Amount must be positive"
}
]
}
}HTTP Status Codes
| Code | Meaning | When |
|---|---|---|
200 | OK | Request succeeded |
400 | Bad Request | Validation failed or a business rule was broken |
401 | Unauthorized | Missing, malformed, or invalid API key |
402 | Payment Required | Card charge was declined |
403 | Forbidden | Live key not yet activated, or 2FA required on a JWT session |
404 | Not Found | Resource does not exist in the current project + mode |
409 | Conflict | Duplicate email, duplicate tax ID, etc. |
422 | Unprocessable | Valid JSON but a semantic rule failed (e.g. no access to the requested project) |
500 | Server Error | Unexpected server error - safe to retry after a delay |
Common Error Codes
| Code | What happened |
|---|---|
INVALID_FIELD | A request body field failed Zod validation. Check error.issues for the specific field. |
NO_ACCESS | The Authorization header is missing or the key is invalid. |
LIVE_KEY_INACTIVE | You used a live key before the project has an active payment provider. |
NOT_FOUND | Resource doesn't exist in this project and mode. |
CHARGE_FAILED | The card was declined by the payment provider. |
SESSION_NOT_PENDING | A checkout or setup session was already completed, canceled, or expired. |
SESSION_EXPIRED | The session is past its expiry window. |
PROJECT_NOT_FOUND | JWT session references a project the user has no role on. |
EMAIL_EXISTS | A customer with that email already exists in this project. |
Retries
Ching does not yet honor an Idempotency-Key header, so retrying a POST may create duplicate resources. For charges and subscriptions, wait for the response before retrying. For reads, retry with exponential backoff (1s, 2s, 4s...) on 5xx responses.