Skip to main content
CornectAPI Docsv1
Sign inGet an API tokenGet started free

Errors

The API uses conventional HTTP status codes and returns a consistent JSON error envelope you can branch on programmatically.

Error envelope#

Every error response carries a detail object with a stable code, a human-readable message, and optional details:

json
{
  "detail": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "This endpoint requires a read_write token.",
    "details": { "token_scope": "read", "required": "read_write" }
  }
}

Status codes#

StatusMeaning
401Missing or invalid token.
402Insufficient credits for a charged operation.
403Authenticated, but the token's scope is insufficient.
404The requested resource doesn't exist.
422Request body or parameters failed validation.
429Rate limit exceeded — back off and retry.

Error codes#

The code values you may encounter:

CodeStatusWhen
MISSING_AUTH_HEADER401No Authorization header sent.
INVALID_TOKEN_FORMAT401Header present but not a cornect_ bearer token.
INVALID_TOKEN401Token not found or revoked.
TOKEN_EXPIRED401Token is past its expiry date.
TOKEN_NOT_WORKSPACE_BOUND401Legacy token with no workspace — mint a new one.
NOT_WORKSPACE_MEMBER401Token owner was removed from the workspace.
INSUFFICIENT_SCOPE403A read token called a read_write endpoint.
INVALID_CURSOR422A pagination cursor that was edited, hand-built, or is no longer valid. Restart from the first page.
CURSOR_SORT_MISMATCH422A cursor reused under a different sort than the one that produced it. Restart, or re-send the original sort_by/sort_order.
RATE_LIMITED429Too many requests in the window. Honour Retry-After; see Rate Limits.
no_candidates400An export matched no companies — there is nothing to export.
insufficient_balance402Your balance cannot cover the export. details carries shortfall, cost, balance and matched_count. Nothing is charged.
insufficient_balance means nothing was charged

Exports are all or nothing. A balance that cannot cover the cost — zero, or merely short — returns this error and applies nothing: no credits charged, no job created, no companies unlocked. There is no partial export and no truncated file.

Read detail.details.shortfall to tell the user how many more credits they need; cost, balance and matched_count are there too, so no second call is needed. To avoid the round trip, call Preview Export first — it is free, and its would_skip is the same shortfall.

Why some codes are UPPERCASE and some are lowercase#

The casing is a convention, not an inconsistency, and it tells you what kind of failure you are looking at:

CasingMeaningExamples
UPPER_SNAKEA protocol or validation failure — the request itself is wrong and will keep failing until you change it.MISSING_AUTH_HEADER, INVALID_TOKEN, INSUFFICIENT_SCOPE, INVALID_CURSOR
lower_snakeA business outcome — the request was perfectly well-formed, and this is the answer.no_candidates, insufficient_balance

A lowercase code is not an error in your integration: a search that matches nothing, or an account that has run out of credits, is a legitimate state your code should handle rather than a bug to fix. Match on the exact string either way — the codes are stable, and we will not re-case an existing one, because that would break every client already branching on it.

Handling errors in code#

Branch on detail.code rather than parsing messages. A robust handler distinguishes auth failures (fix the token), scope/credit failures (fix the request or top up), and 429 (retry with backoff):

Note on 429: its body carries the envelope like every other error, with detail.code = "RATE_LIMITED". For a transition period it also carries a top-level error string duplicating the message — that key is deprecated and will be removed; read detail.
async function call(url, init) {
  const res = await fetch(url, init);
  if (res.ok) return res.json();
  if (res.status === 429) throw new Error("Rate limited — retry with backoff");
  const { detail = {} } = await res.json().catch(() => ({}));
  switch (detail.code) {
    case "INVALID_TOKEN":
    case "TOKEN_EXPIRED":
      throw new Error("Auth failed — check your token");
    case "INSUFFICIENT_SCOPE":
      throw new Error("Use a read_write token");
    case "insufficient_balance":
      throw new Error("Out of credits — top up at /account/credits");
    default:
      throw new Error(detail.message || `HTTP ${res.status}`);
  }
}

For deeper retry/idempotency guidance see the Error Handling guide.