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
| Property | Type | Description |
|---|---|---|
code | string | Machine-readable error code |
message | string | Human-readable error description |
details | object | Additional context (optional) |
HTTP Status Codes
Coherence uses standard HTTP status codes to indicate the result of API requests.
Client Errors (4xx)
| Status | Name | Description |
|---|---|---|
| 400 | Bad Request | The request was malformed or contains invalid data |
| 401 | Unauthorized | Authentication failed or credentials missing |
| 403 | Forbidden | Valid credentials but insufficient permissions |
| 404 | Not Found | The requested resource does not exist |
| 409 | Conflict | The request conflicts with existing data |
| 422 | Unprocessable Entity | Request syntax is valid but semantically incorrect |
| 429 | Too Many Requests | Rate limit exceeded |
Server Errors (5xx)
| Status | Name | Description |
|---|---|---|
| 500 | Internal Server Error | An unexpected error occurred on our servers |
| 502 | Bad Gateway | Temporary network issue |
| 503 | Service Unavailable | Service temporarily unavailable |
| 504 | Gateway Timeout | Request 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
validation_error | 400 | Request data failed validation | Check the details object for specific field errors |
invalid_field | 400 | A field value is invalid for its type | Ensure the value matches the expected field type |
required_field | 400 | A required field is missing | Include all required fields in your request |
invalid_format | 400 | Field value format is incorrect | Check date, email, or phone formats |
invalid_json | 400 | Request body is not valid JSON | Validate JSON syntax before sending |
invalid_query | 400 | Query parameters are malformed | Check 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
unauthorized | 401 | No authentication credentials provided | Include Authorization header with valid token |
invalid_token | 401 | The provided token is invalid | Check that your API key or access token is correct |
token_expired | 401 | The access token has expired | Refresh your token using the refresh token |
token_revoked | 401 | The token has been revoked | Generate 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
forbidden | 403 | Access to resource is denied | Verify your account has access to this resource |
insufficient_permissions | 403 | Token lacks required permissions | Use an API key or token with appropriate scopes |
scope_required | 403 | OAuth token missing required scope | Request 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
not_found | 404 | The requested resource was not found | Verify the resource ID and endpoint path |
record_not_found | 404 | The specified record does not exist | Check the record ID is correct |
module_not_found | 404 | The specified module does not exist | Verify the module slug in your request |
field_not_found | 404 | The specified field does not exist | Check field name against module schema |
user_not_found | 404 | The specified user does not exist | Verify 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
conflict | 409 | The request conflicts with current state | Refresh data and retry with updated values |
duplicate_record | 409 | A record with this data already exists | Use the existing record or update it |
uniqueness_violation | 409 | A unique field constraint was violated | Use a unique value for the specified field |
version_conflict | 409 | Record was modified by another request | Fetch 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
rate_limit_exceeded | 429 | Too many requests in time window | Wait 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
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
internal_error | 500 | An unexpected error occurred | Retry the request; contact support if it persists |
service_unavailable | 503 | Service temporarily unavailable | Wait and retry with exponential backoff |
timeout | 504 | Request processing timed out | Retry 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}")
raisecURL 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 Code | Retry Strategy |
|---|---|
rate_limit_exceeded | Wait for Retry-After seconds, then retry |
internal_error | Retry with exponential backoff |
service_unavailable | Retry with exponential backoff |
timeout | Retry with exponential backoff |
token_expired | Refresh token, then retry immediately |
Non-Retryable Errors
These errors require code changes or user action:
| Error Code | Required Action |
|---|---|
validation_error | Fix the request data |
invalid_token | Use correct credentials |
forbidden | Request appropriate permissions |
not_found | Verify resource exists |
duplicate_record | Use 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