Error Handling

Cord uses conventional HTTP codes and a predictable error structure to indicate success or failure.

HTTP Status Codes

The Cord API always returns standard HTTP status codes. As a general rule:

  • 2xx indicates success.
  • 4xx indicates an error originating from the provided information (e.g., missing parameters, invalid token).
  • 5xx indicates an error on Cord’s servers (these are very rare).

Code Summary

Code Description
200 OK Everything worked as expected.
400 Bad Request The request was unacceptable. Usually means invalid_request (missing fields) or invalid_json.
401 Unauthorized Your API Key is missing, incorrect, or was revoked.
402 Payment Required The operation requires a higher plan (subscription_required) or exceeds a limit on your current plan (plan_limit_reached, e.g. number of clients or products).
403 Forbidden You have insufficient permissions (e.g., trying to write with a read-only key, or a Publishable Key attempting an action outside its scope).
404 Not Found The resource (quote, client, invoice) does not exist.
409 Conflict An invalid state transition for the resource (invalid_state) — for example, approving a quote that is still a draft, voiding an invoice that requires a credit note instead, or trying to delete test data (/api/test-mode/reset) without being in a Sandbox organization. Also returned while another request with the same Idempotency-Key is still running (idempotency_in_progress).
422 Unprocessable Entity You reused an Idempotency-Key with a different request (idempotency_key_reused).
429 Too Many Requests You exceeded your key’s requests-per-minute limit (rate_limited) or your plan’s monthly quota (api_quota_exceeded). Respect the Retry-After header.
503 Service Unavailable A dependency the operation needs didn’t respond — for example, an exchange rate could not be obtained (fx_unavailable) or the tax catalog (tax_catalog_unavailable). Cord never invents a rate: if it can’t prove one, the operation fails closed. Retry later.

Error Object Structure

When a 4xx or 5xx error occurs, Cord always responds with a flat (not nested) JSON object with two fields: error (a human-readable message you can show or log) and code (a stable string for your handling logic). Some errors add extra fields depending on the case (see examples below).

Error Response Example:

{
  "error": "The company name is required",
  "code": "invalid_request"
}

Example with extra fields (plan limit, 402):

{
  "error": "Your plan allows 50 clients. Free up space or upgrade your plan to continue.",
  "code": "plan_limit_reached",
  "resource": "clients",
  "limit": 50,
  "plan": "starter"
}

Don’t rely on the nested error.code / error.message shape to parse the response: error is always the message string, and code is a sibling field at the same level, not a property of error.

Common Error Codes (code)

  • invalid_json: Your POST request body is not valid JSON, or Content-Type: application/json wasn’t sent.

  • invalid_request: A business validation failed (missing field, discount out of range, a quote without a valid client). The error field explains the exact reason.

  • missing_key / invalid_key: The Authorization: Bearer header is missing, or the key doesn’t exist / was revoked.

  • insufficient_scope: Your key doesn’t have the required scope (a read-only key tried to write, or a Publishable Key tried a route outside its allowed list).

  • not_found: The requested resource doesn’t exist in your organization.

  • subscription_required: The capability requires a higher plan. The response includes plan (your current one) and plan_required.

  • plan_limit_reached: You hit a hard limit on your plan (clients, products, API keys). The response includes resource, limit, and plan.

  • integration_limit_reached (403): your integrations already created the 100 webhook subscriptions allowed per organization. Delete the ones you no longer use.

  • rate_limited / api_quota_exceeded: Requests-per-minute or monthly API quota limit. Retry while respecting Retry-After.

  • fx_unavailable / tax_catalog_unavailable: A third-party data point (exchange rate or tax catalog) could not be obtained with certainty. The operation did not complete — don’t assume it did.

  • invalid_state: The resource is not in a status that allows the operation, or it changed while we were processing it. Nothing was applied.

  • invalid_url: A webhook URL is not https:// or points to an internal host.

  • invalid_idempotency_key / idempotency_key_reused / idempotency_in_progress / idempotency_unavailable: See the next section.

Safe retries with Idempotency-Key

If a request that creates or changes something (POST, PATCH, DELETE) is cut off by the network or a timeout, you do not know whether Cord applied it. Send the Idempotency-Key header with a unique value per operation (for example, a UUID you generate) and retry with the same key and the same body:

curl -X POST "https://cordhq.app/api/v1/clientes" \
     -H "Authorization: Bearer sk_live_tU..." \
     -H "Content-Type: application/json" \
     -H "Idempotency-Key: 5d9c2b7e-6f1a-4c3d-9e8b-2a1f0c7d6e5b" \
     -d '{ "empresa": "ACME Corp" }'
  • Retry with the same key and body: Cord returns the original response without running the operation again or counting it against your quota. The repeated response includes the Idempotent-Replayed: true header.
  • Same key with a different body, method, or route: 422 idempotency_key_reused. Use a new key for each different operation.
  • The first request has not finished: 409 idempotency_in_progress. Wait and retry.
  • 5xx and 429 errors: are not stored, so you can retry with the same key.
  • The key is scoped to your API key, accepts 1 to 255 visible characters (invalid_idempotency_key otherwise), and expires after 24 hours.
  • If Cord cannot reserve the key, it returns 503 idempotency_unavailable without running the operation.

The header is optional: without it, every request runs as it arrives.