Errors

Understand API error responses and how to handle them

When an API request fails, Coherence returns a structured error response with details to help you diagnose and resolve the issue.

Error Response Format

All error responses follow a consistent envelope:

{
  "error": {
    "code": "not_found",
    "message": "Record not found",
    "statusCode": 404
  }
}

Validation failures include an issues array describing each failed field:

{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "statusCode": 400,
    "issues": [
      { "path": ["email"], "message": "Invalid email" }
    ]
  }
}

Error Object Properties

PropertyTypeDescription
codestringStable machine-readable error code (see the catalog below)
messagestringHuman-readable error description
statusCodenumberHTTP status code (mirrors the response status)
issuesarrayPresent on validation errors (code: "validation_error") — one entry per failed field
dataobjectAdditional context on some errors (optional)

Branch on code for error handling — it is a stable, machine-readable string. statusCode mirrors the HTTP status and remains available; message is for humans and may change wording over time.

Error Codes

Every error response carries a code from this catalog:

CodeHTTP StatusMeaningRetry?
bad_request400Malformed or invalid requestNo — fix the request
validation_error400Request failed schema validation; inspect issuesNo — fix the request
unauthorized401Authentication failed or credentials missingNo — fix credentials
forbidden403Valid credentials but insufficient permissions or scopeNo — fix permissions
not_found404The requested resource does not existNo
conflict409The request conflicts with existing dataNo — resolve the conflict
unprocessable_entity422Request syntax is valid but semantically incorrectNo — fix the request
rate_limit_exceeded429Rate limit exceededYes — wait Retry-After seconds
internal_error500An unexpected error occurred on our serversYes — exponential backoff
bad_gateway502Temporary network issueYes — exponential backoff
service_unavailable503Service temporarily unavailableYes — exponential backoff
gateway_timeout504Request timed outYes — exponential backoff

Any other 4xx status maps to bad_request and any other 5xx maps to internal_error. The catalog is additive-only: codes are never renamed or removed, but individual endpoints may emit new, more specific codes over time — treat unknown codes as you would their statusCode class.

HTTP Status Codes

Coherence uses standard HTTP status codes to indicate the result of API requests.

Client Errors (4xx)

StatusNameDescription
400Bad RequestThe request was malformed or contains invalid data
401UnauthorizedAuthentication failed or credentials missing
403ForbiddenValid credentials but insufficient permissions
404Not FoundThe requested resource does not exist
409ConflictThe request conflicts with existing data
422Unprocessable EntityRequest syntax is valid but semantically incorrect
429Too Many RequestsRate limit exceeded

Server Errors (5xx)

StatusNameDescription
500Internal Server ErrorAn unexpected error occurred on our servers
502Bad GatewayTemporary network issue
503Service UnavailableService temporarily unavailable
504Gateway TimeoutRequest timed out

Server errors (5xx) are rare and typically transient. If you encounter them persistently, check our status page or contact support.

Handling Errors

Branch on the machine-readable code field, or on the HTTP status (the statusCode field mirrors it):

const response = await fetch(
  'https://api.getcoherence.io/v1/modules/contacts/records',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ fields: { name: 'John Smith' } })
  }
);
 
if (!response.ok) {
  const body = await response.json().catch(() => null);
  const statusCode = body?.error?.statusCode ?? response.status;
  const code = body?.error?.code;
 
  if (code === 'validation_error') {
    // Inspect the issues array
    console.error('Validation failed:', body?.error?.issues);
    return;
  }
 
  switch (statusCode) {
    case 400:
      console.error('Bad request:', body?.error?.message);
      break;
    case 401:
      console.error('Check your API key:', body?.error?.message);
      break;
    case 403:
      console.error('Missing permission or scope:', body?.error?.message);
      break;
    case 404:
      console.error('Resource not found:', body?.error?.message);
      break;
    case 429: {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '1', 10);
      console.warn(`Rate limited. Retry in ${retryAfter}s`);
      break;
    }
    default:
      console.error(`API error ${statusCode}:`, body?.error?.message);
  }
}

Validation Errors (400)

When request validation fails, the envelope carries code: "validation_error" and the issues array lists every problem. Each entry has a path (the location of the invalid value) and a message, along with additional context fields describing the specific check that failed:

{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "statusCode": 400,
    "issues": [
      {
        "code": "too_big",
        "maximum": 100,
        "type": "number",
        "inclusive": true,
        "path": ["pageSize"],
        "message": "Number must be less than or equal to 100"
      },
      {
        "code": "invalid_enum_value",
        "options": ["asc", "desc"],
        "received": "up",
        "path": ["sortDirection"],
        "message": "Invalid enum value. Expected 'asc' | 'desc', received 'up'"
      }
    ]
  }
}

Use path to map each issue back to the offending parameter or body field, and surface message to your users or logs. Fix the request before retrying — validation errors are never transient.

Rate Limit Errors (429)

429 responses include a Retry-After header (in seconds) alongside the standard envelope:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706745600
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after the window resets.",
    "statusCode": 429
  }
}

Wait at least Retry-After seconds before retrying. See Rate Limiting & Best Practices for backoff patterns and header details.

Retry Guidance

Not every error should be retried:

StatusRetry?Approach
400, 401, 403, 404, 409, 422NoFix the request, credentials, or permissions first
429YesWait for Retry-After seconds, then retry
500, 502, 503, 504YesRetry with exponential backoff and jitter

GET requests are always safe to retry. Be careful with mutations (POST, PATCH, DELETE): if a request failed after the server may have processed it (a timeout or 5xx), blindly retrying can create duplicates — check whether the operation actually succeeded before resending.

async function withRetry<T>(
  fn: () => Promise<Response>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<Response> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fn();
 
    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '1', 10);
      await sleep(retryAfter * 1000);
      continue;
    }
 
    if (response.status >= 500) {
      // Exponential backoff with jitter
      const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
      await sleep(delay);
      continue;
    }
 
    return response; // Success or a non-retryable client error
  }
 
  throw new Error('Max retries exceeded');
}
 
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

For programmatic retry logic, the code field is the most reliable signal: retry only on rate_limit_exceeded, internal_error, bad_gateway, service_unavailable, and gateway_timeout.


Related: API Overview | Authentication | Rate Limiting & Best Practices