Errors ⚠️
The Cardda API uses standard HTTP status codes. The body of every error response is JSON and follows one of two shapes:
{ "error": "<machine_code>", "message": "<human readable description>" }{ "errors": { "field_name": ["error 1", "error 2"], ... } }The first shape is used for transport-level errors (auth, authorization, rate limit). message is optional — some terse responses (e.g. the bare unauthorized you get when company-id is missing) ship only the error code. Always read error for branching logic, and treat message as an optional human-readable hint. The second shape is used for validation errors on POST / PATCH / PUT requests, so you can map them back to specific form fields.
Status code reference
| HTTP | error code | When you see this | Notes |
|---|---|---|---|
400 | bad_request | The request body or query string is malformed (invalid JSON, wrong types, missing required field). | Inspect the message field. Fix and retry. |
401 | unauthenticated | Missing or invalid Authorization header. | Verify the API key is active and not revoked. |
401 | email_not_verified | The user owning the API key has not verified their email. | Ask the user to confirm their email. |
401 | unauthorized | The company-id header was required but absent. | Add the header. See The company-id header. |
403 | forbidden | The user is authenticated but the policy denies this action (insufficient role, the resource belongs to another company, etc.). | Check the user's role in the company. |
404 | not_found | Resource not found, or company-id references a company the user is not a member of. | We do not leak existence — 404 is returned in both cases by design. |
409 | conflict | The action conflicts with current state (e.g. trying to authorize a transaction that is already authorized, enrolling a recipient that already exists). | Re-fetch the resource and reconcile. |
422 | unprocessable_entity | Validation failed on POST / PATCH / PUT. | Body uses the { errors: { field: [...] } } shape. |
429 | rate_limit_exceeded | Too many requests. | See Rate limits and inspect the Retry-After header. |
500 | internal_error | Unexpected error on Cardda's side. | Retry with exponential backoff; if it persists, contact us with the X-Request-Id response header. |
502 / 503 / 504 | upstream/transient | Temporary upstream issue (a banking partner, a card issuer). | Retry with backoff. |
Examples
Missing company-id
company-id$ curl -i https://api.cardda.com/v1/banking/bank_transactions \
-H "Authorization: Bearer YOUR_API_KEY"
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{"error": "unauthorized"}Validation error on create
$ curl -i -X POST https://api.cardda.com/v1/banking/bank_recipients \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "company-id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Vendor"}'
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"errors": {
"rut": ["can't be blank", "is invalid"],
"account_number": ["can't be blank"],
"bank_id": ["must reference an existing bank"]
}
}Rate limited
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 1 second.",
"retry_after": 1
}Resource not found / not your company
HTTP/1.1 404 Not Found
Content-Type: application/json
{"error": "not_found"}Recommended client-side handling
async function carddaRequest(path, init) {
const res = await fetch(`https://api.cardda.com${path}`, init);
if (res.ok) return res.json();
let body = {};
try { body = await res.json(); } catch {}
switch (res.status) {
case 401:
// Ask the user to re-authenticate or refresh the company list
throw new AuthError(body);
case 403:
throw new PermissionError(body);
case 404:
throw new NotFoundError(body);
case 422:
throw new ValidationError(body.errors);
case 429: {
const retryAfter = Number(res.headers.get("Retry-After") ?? "1");
throw new RateLimitError({ retryAfter });
}
case 500:
case 502:
case 503:
case 504:
throw new TransientError(body, { requestId: res.headers.get("X-Request-Id") });
default:
throw new HttpError(res.status, body);
}
}Tracing an error
Every Cardda response includes an X-Request-Id header. When you contact support about a failing request, paste that ID in the message — we can pull the full trace from our observability tooling instantly.
$ curl -i https://api.cardda.com/v1/banking/bank_transactions ...
HTTP/1.1 500 Internal Server Error
X-Request-Id: 8f3c2a7e-1b4d-4f9a-b2c5-9d7e8f1a2b3cTip. Log the
X-Request-Idfor every non-2xx response in your client. Future-you will thank past-you.
Updated 1 day ago
