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

HTTPerror codeWhen you see thisNotes
400bad_requestThe request body or query string is malformed (invalid JSON, wrong types, missing required field).Inspect the message field. Fix and retry.
401unauthenticatedMissing or invalid Authorization header.Verify the API key is active and not revoked.
401email_not_verifiedThe user owning the API key has not verified their email.Ask the user to confirm their email.
401unauthorizedThe company-id header was required but absent.Add the header. See The company-id header.
403forbiddenThe 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.
404not_foundResource 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.
409conflictThe 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.
422unprocessable_entityValidation failed on POST / PATCH / PUT.Body uses the { errors: { field: [...] } } shape.
429rate_limit_exceededToo many requests.See Rate limits and inspect the Retry-After header.
500internal_errorUnexpected error on Cardda's side.Retry with exponential backoff; if it persists, contact us with the X-Request-Id response header.
502 / 503 / 504upstream/transientTemporary upstream issue (a banking partner, a card issuer).Retry with backoff.

Examples

Missing 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-9d7e8f1a2b3c

Tip. Log the X-Request-Id for every non-2xx response in your client. Future-you will thank past-you.