Error Handling

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 format:

{
  "error": {
    "code": "validation_error",
    "message": "The email field is required",
    "details": {
      "field": "email",
      "reason": "required"
    }
  }
}

Error Object Properties

PropertyTypeDescription
codestringMachine-readable error code
messagestringHuman-readable error description
detailsobjectAdditional context (optional)

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.

Error Codes Reference

Validation Errors

CodeHTTP StatusDescriptionResolution
validation_error400Request data failed validationCheck the details object for specific field errors
invalid_field400A field value is invalid for its typeEnsure the value matches the expected field type
required_field400A required field is missingInclude all required fields in your request
invalid_format400Field value format is incorrectCheck date, email, or phone formats
invalid_json400Request body is not valid JSONValidate JSON syntax before sending
invalid_query400Query parameters are malformedCheck filter and sort parameter syntax

Example - Validation Error:

{
  "error": {
    "code": "validation_error",
    "message": "Validation failed for 2 fields",
    "details": {
      "errors": [
        {
          "field": "email",
          "code": "invalid_format",
          "message": "Must be a valid email address"
        },
        {
          "field": "phone",
          "code": "required_field",
          "message": "Phone number is required"
        }
      ]
    }
  }
}

Authentication Errors

CodeHTTP StatusDescriptionResolution
unauthorized401No authentication credentials providedInclude Authorization header with valid token
invalid_token401The provided token is invalidCheck that your API key or access token is correct
token_expired401The access token has expiredRefresh your token using the refresh token
token_revoked401The token has been revokedGenerate a new API key or re-authenticate

Example - Token Expired:

{
  "error": {
    "code": "token_expired",
    "message": "Your access token has expired",
    "details": {
      "expired_at": "2024-01-15T10:30:00Z"
    }
  }
}

Authorization Errors

CodeHTTP StatusDescriptionResolution
forbidden403Access to resource is deniedVerify your account has access to this resource
insufficient_permissions403Token lacks required permissionsUse an API key or token with appropriate scopes
scope_required403OAuth token missing required scopeRequest additional scopes during authorization

Example - Insufficient Permissions:

{
  "error": {
    "code": "insufficient_permissions",
    "message": "Your API key does not have permission to delete records",
    "details": {
      "required_permission": "delete",
      "current_permissions": ["read", "write"]
    }
  }
}

Resource Errors

CodeHTTP StatusDescriptionResolution
not_found404The requested resource was not foundVerify the resource ID and endpoint path
record_not_found404The specified record does not existCheck the record ID is correct
module_not_found404The specified module does not existVerify the module slug in your request
field_not_found404The specified field does not existCheck field name against module schema
user_not_found404The specified user does not existVerify the user ID

Example - Record Not Found:

{
  "error": {
    "code": "record_not_found",
    "message": "Record not found",
    "details": {
      "module": "contacts",
      "record_id": "rec_abc123"
    }
  }
}

Conflict Errors

CodeHTTP StatusDescriptionResolution
conflict409The request conflicts with current stateRefresh data and retry with updated values
duplicate_record409A record with this data already existsUse the existing record or update it
uniqueness_violation409A unique field constraint was violatedUse a unique value for the specified field
version_conflict409Record was modified by another requestFetch latest version and retry

Example - Uniqueness Violation:

{
  "error": {
    "code": "uniqueness_violation",
    "message": "A record with this email already exists",
    "details": {
      "field": "email",
      "value": "[email protected]",
      "existing_record_id": "rec_xyz789"
    }
  }
}

Rate Limiting Errors

CodeHTTP StatusDescriptionResolution
rate_limit_exceeded429Too many requests in time windowWait and retry using the Retry-After header

Example - Rate Limit Exceeded:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please retry after 60 seconds",
    "details": {
      "limit": 1000,
      "window": "1 minute",
      "retry_after": 60
    }
  }
}

Rate limit headers are included in the response:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1609459200
Retry-After: 60

Server Errors

CodeHTTP StatusDescriptionResolution
internal_error500An unexpected error occurredRetry the request; contact support if it persists
service_unavailable503Service temporarily unavailableWait and retry with exponential backoff
timeout504Request processing timed outRetry the request or reduce payload size

Example - Internal Error:

{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred. Please try again.",
    "details": {
      "request_id": "req_abc123xyz"
    }
  }
}

When reporting issues to support, include the request_id from the error response for faster diagnosis.

Handling Errors in Code

JavaScript/TypeScript

import { CoherenceClient, CoherenceError } from '@coherence/sdk';
 
const client = new CoherenceClient({ apiKey: 'YOUR_API_KEY' });
 
async function createContact(data: ContactData) {
  try {
    const contact = await client.modules.contacts.records.create(data);
    return contact;
  } catch (error) {
    if (error instanceof CoherenceError) {
      switch (error.code) {
        case 'validation_error':
          // Handle field validation errors
          console.error('Validation failed:', error.details.errors);
          break;
        case 'duplicate_record':
          // Handle duplicate - maybe update instead
          console.error('Record already exists:', error.details.existing_record_id);
          break;
        case 'rate_limit_exceeded':
          // Wait and retry
          const retryAfter = error.details.retry_after || 60;
          await sleep(retryAfter * 1000);
          return createContact(data);
        case 'token_expired':
          // Refresh token and retry
          await refreshToken();
          return createContact(data);
        default:
          console.error('API error:', error.message);
      }
    }
    throw error;
  }
}

Python

from coherence import CoherenceClient, CoherenceError
import time
 
client = CoherenceClient(api_key="YOUR_API_KEY")
 
def create_contact(data):
    try:
        contact = client.modules.contacts.records.create(data)
        return contact
    except CoherenceError as error:
        if error.code == "validation_error":
            # Handle field validation errors
            print(f"Validation failed: {error.details['errors']}")
        elif error.code == "duplicate_record":
            # Handle duplicate - maybe update instead
            print(f"Record exists: {error.details['existing_record_id']}")
        elif error.code == "rate_limit_exceeded":
            # Wait and retry
            retry_after = error.details.get("retry_after", 60)
            time.sleep(retry_after)
            return create_contact(data)
        elif error.code == "token_expired":
            # Refresh token and retry
            refresh_token()
            return create_contact(data)
        else:
            print(f"API error: {error.message}")
        raise

cURL Error Checking

response=$(curl -s -w "\n%{http_code}" -X POST \
  "https://api.getcoherence.io/v1/modules/contacts/records" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "John Smith"}')
 
# Extract status code (last line)
http_code=$(echo "$response" | tail -n1)
# Extract body (all but last line)
body=$(echo "$response" | sed '$d')
 
if [ "$http_code" -ge 400 ]; then
  echo "Error $http_code: $(echo $body | jq -r '.error.message')"
  exit 1
fi
 
echo "Success: $(echo $body | jq -r '.data.id')"

Retry Strategies

Not all errors should be retried. Use this guide to determine the appropriate action.

Retryable Errors

These errors are typically transient and can be retried:

Error CodeRetry Strategy
rate_limit_exceededWait for Retry-After seconds, then retry
internal_errorRetry with exponential backoff
service_unavailableRetry with exponential backoff
timeoutRetry with exponential backoff
token_expiredRefresh token, then retry immediately

Non-Retryable Errors

These errors require code changes or user action:

Error CodeRequired Action
validation_errorFix the request data
invalid_tokenUse correct credentials
forbiddenRequest appropriate permissions
not_foundVerify resource exists
duplicate_recordUse existing record or change data

Exponential Backoff

For transient errors, implement exponential backoff:

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  let lastError: Error;
 
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
 
      // Check if error is retryable
      if (error instanceof CoherenceError) {
        const retryableCodes = [
          'internal_error',
          'service_unavailable',
          'timeout'
        ];
 
        if (!retryableCodes.includes(error.code)) {
          throw error; // Non-retryable, fail immediately
        }
 
        // For rate limits, use the provided retry time
        if (error.code === 'rate_limit_exceeded') {
          const retryAfter = error.details?.retry_after || 60;
          await sleep(retryAfter * 1000);
          continue;
        }
      }
 
      // Exponential backoff with jitter
      const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
      console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
      await sleep(delay);
    }
  }
 
  throw lastError;
}
 
// Usage
const contact = await withRetry(() =>
  client.modules.contacts.records.create({ name: 'John' })
);

Circuit Breaker Pattern

For high-volume applications, consider implementing a circuit breaker:

class CircuitBreaker {
  private failures = 0;
  private lastFailure: number = 0;
  private state: 'closed' | 'open' | 'half-open' = 'closed';
 
  constructor(
    private threshold: number = 5,
    private resetTimeout: number = 30000
  ) {}
 
  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailure > this.resetTimeout) {
        this.state = 'half-open';
      } else {
        throw new Error('Circuit breaker is open');
      }
    }
 
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
 
  private onSuccess() {
    this.failures = 0;
    this.state = 'closed';
  }
 
  private onFailure() {
    this.failures++;
    this.lastFailure = Date.now();
    if (this.failures >= this.threshold) {
      this.state = 'open';
    }
  }
}

Best Practices

Log Error Details

Always log the full error response for debugging:

catch (error) {
  console.error('API Error:', {
    code: error.code,
    message: error.message,
    details: error.details,
    requestId: error.details?.request_id
  });
}

Handle Specific Errors First

Check for specific error codes before falling back to generic handling:

if (error.code === 'duplicate_record') {
  // Specific handling
} else if (error.status === 400) {
  // Generic client error handling
} else {
  // Fallback
}

Provide User-Friendly Messages

Translate technical errors into user-friendly messages:

const userMessages: Record<string, string> = {
  'validation_error': 'Please check your input and try again.',
  'rate_limit_exceeded': 'Too many requests. Please wait a moment.',
  'internal_error': 'Something went wrong. Please try again later.',
  'not_found': 'The requested item could not be found.',
};
 
const friendlyMessage = userMessages[error.code] || 'An error occurred.';

Related: API Overview | Authentication