Error Handling Reference
Complete guide to error handling in the Arkipel API.
Overview
The Arkipel API uses standard HTTP status codes and provides descriptive error messages to help you diagnose and resolve issues. Most errors return simple message strings. Only specific protection errors include a machine-readable code.
This guide covers:
- HTTP status codes and their meanings
- Error response formats
- Machine-readable error codes (limited)
- Retry strategies
- Best practices for error handling
HTTP Status Codes
| Status Code | Meaning | When to Retry | Client Action |
|---|---|---|---|
| 200 | OK | No | Process response normally |
| 201 | Created | No | Resource created successfully |
| 202 | Accepted | N/A | Async operation accepted - poll for status |
| 400 | Bad Request | No | Fix request format or parameters |
| 401 | Unauthorized | No | Check authentication credentials |
| 403 | Forbidden | No | Check permissions or contact admin |
| 404 | Not Found | No | Verify resource ID exists |
| 422 | Unprocessable Entity | No | Fix validation errors in request |
| 429 | Too Many Requests | Yes | Wait and retry (respect Retry-After header) |
| 500 | Internal Server Error | Yes | Retry with exponential backoff |
| 502 | Bad Gateway | Yes | Retry with exponential backoff |
| 503 | Service Unavailable | Yes | Retry with exponential backoff |
| 504 | Gateway Timeout | Yes | Retry with exponential backoff |
Error Response Formats
Simple Error (Most Common)
Most errors return a simple string message:
{
"error": "Invalid JSON payload"
}
{
"error": "Unauthorized: Invalid signature"
}
{
"error": "Unauthorized: source_public_key not whitelisted"
}
}
Rate Limit Error
When you exceed the rate limit (100 requests per minute):
{
"error": "Rate limit exceeded: 100 requests per minute",
"retry_after": 45
}
Response Headers:
Retry-After: 45
See API Connection for detailed rate limiting documentation.
Protected Record Error
When attempting to delete a protected record (the only error with a machine-readable code):
{
"error": "Cannot delete person that is a member of this account",
"code": "PROTECTED_RECORD"
}
{
"error": "Cannot delete the account's main organization",
"code": "PROTECTED_RECORD"
}
Async Processing Error
When querying an async message that failed:
{
"payload": {
"type": "arkipel_messages:query",
"message_id": "abc123",
"status": "failed",
"error": "Validation failed: email has already been taken"
}
}
Not Found Error
When a requested resource doesn’t exist:
{
"payload": { "type": "arkipel_messages:query" },
"error": "No message found with payload#message_id='invalid-id'",
"status": :not_found
}
Common Error Messages
Authentication Errors
| Error Message | Cause | Resolution |
|---|---|---|
Empty request body | Request has no body | Include valid JSON payload |
Invalid JSON payload | Malformed JSON | Fix JSON syntax |
Unauthorized: Invalid signature | Signature verification failed | Check keypair and signing process |
Unauthorized: source_public_key not whitelisted | Public key not authorized | Contact community admin for access |
Unauthorized: Message type not allowed | Whitelist doesn’t permit this message | Check whitelisted message types |
Validation Errors
Validation errors are returned as strings describing the specific issue:
{
"error": "Validation failed: import_id has already been taken"
}
{
"error": "Validation failed: first_name can't be blank"
}
{
"error": "Validation failed: country_code can't be blank"
}
Common validation patterns:
-
Validation failed: {field} can't be blank- Required field missing -
Validation failed: {field} has already been taken- Duplicate unique value -
Validation failed: {field} is invalid- Format validation failed -
Validation failed: {field} is too long- Exceeds maximum length
Protected Resource Errors
| Error Message | Code | Cause | Resolution |
|---|---|---|---|
Cannot delete person that is a member of this account | PROTECTED_RECORD | Person has active membership | Remove membership first |
Cannot delete the account's main organization | PROTECTED_RECORD | Attempting to delete main org | Cannot delete - contact admin |
Async Operation Status
When querying async operations via arkipel_messages:query, check the status field:
{
"payload": {
"type": "arkipel_messages:query",
"message_id": "abc123",
"status": "failed",
"error": "Validation failed: email format is invalid"
}
}
Status Values
| Status | Meaning | Has Error | Has Resource |
|---|---|---|---|
pending | Waiting to be processed | No | No |
processing | Currently being processed | No | No |
persisted | Successfully completed | No | Yes |
failed | Processing failed | Yes | No |
rejected | Rejected by policy/user | Yes | No |
Note: The status field in async responses uses string values, not error codes.
Retry Strategies
When NOT to Retry
These errors require code or configuration changes:
| Status | Error Type | Action Required |
|---|---|---|
| 400 | Bad Request | Fix request format |
| 401 | Unauthorized | Update credentials |
| 403 | Forbidden | Get permissions |
| 404 | Not Found | Verify resource exists |
| 422 | Validation Error | Fix data |
When to Retry
These errors may resolve on retry:
| Status | Error Type | Strategy |
|---|---|---|
| 429 | Rate Limit | Wait retry_after seconds |
| 500 | Server Error | Exponential backoff, 3 retries |
| 502 | Bad Gateway | Exponential backoff, 3 retries |
| 503 | Service Unavailable | Exponential backoff, 3 retries |
| 504 | Timeout | Exponential backoff, 3 retries |
Exponential Backoff
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
// Attempt 1: 1s, Attempt 2: 2s, Attempt 3: 4s, ... max 30s
Add jitter to prevent thundering herd:
const jitter = Math.random() * 1000;
const delay = Math.min(1000 * Math.pow(2, attempt), 30000) + jitter;
Best Practices
1. Check HTTP Status First
Always check HTTP status before parsing response body:
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
await sleep(retryAfter * 1000);
retry();
} else if (response.status >= 500) {
// Retry with backoff
} else if (response.status === 422) {
// Parse validation errors
}
2. Parse Error Messages
Extract specific information from error strings:
const error = response.error;
if (error.includes('Validation failed')) {
// Extract field name
const match = error.match(/Validation failed: (\w+)/);
const field = match ? match[1] : 'unknown';
showFieldError(field, error);
}
3. Handle Protected Records
Check for the machine-readable code when deleting:
if (response.code === 'PROTECTED_RECORD') {
// Cannot delete - show appropriate message
showError('This record is protected and cannot be deleted');
}
4. Implement Idempotency
Use consistent import_id to prevent duplicates:
// Same import_id = same result, safe to retry
const import_id = `my_system_${recordId}`;
await client.createPerson({ import_id, ...data });
5. Log Errors
Always log full error responses for debugging:
console.error('API Error:', {
status: response.status,
error: response.error,
code: response.code || null
});
Troubleshooting Guide
“Unauthorized: Invalid signature”
Possible causes:
- Signature generated incorrectly
- Wrong private key
- Payload modified after signing
- Clock drift (timestamp too old)
Solutions:
- Verify signing algorithm (Ed25519)
- Check private key matches public key
- Sign exact payload JSON without modification
- Ensure timestamp is recent
“Rate limit exceeded”
Solutions:
- Implement client-side rate limiting (stay under 100/min)
- Add request queueing
- Use exponential backoff
- Contact admin for whitelist exemption
“Validation failed: {field}…”
Common issues:
- Required field missing
- Duplicate
import_id - Invalid email format
- String too long (>255 chars)
Solutions:
- Check field constraints in spec
- Validate data before sending
- Use unique import_ids
- Keep strings under 255 characters
Async operation stuck in “pending”
Solutions:
- Continue polling (normal for long operations)
- Check status every 2-5 seconds
- Timeout after reasonable period (30-60s)
- Contact admin if stuck >5 minutes
SDK Error Handling Examples
JavaScript
try {
const response = await client.createPerson(data);
} catch (error) {
if (error.status === 429) {
await sleep(error.retry_after * 1000);
retry();
} else if (error.code === 'PROTECTED_RECORD') {
console.error('Cannot delete protected record');
} else if (error.message?.includes('Validation failed')) {
console.error('Validation error:', error.message);
} else if (error.status >= 500) {
// Retry with backoff
}
}
Ruby
begin
response = client.create_person(data)
rescue ArkipelRateLimitError => e
sleep(e.retry_after)
retry
rescue ArkipelError => e
if e.code == 'PROTECTED_RECORD'
Rails.logger.error 'Protected record cannot be deleted'
elsif e.message.include?('Validation failed')
Rails.logger.error "Validation error: #{e.message}"
end
end
Summary
- Most errors are simple string messages without codes
-
Only
PROTECTED_RECORDhas a machine-readablecodefield -
Async operations use
statusfield (pending/processing/persisted/failed/rejected) -
Rate limits include
retry_afterfor proper handling - Validation errors describe the specific field issue
For HTTP status-based handling, see Global Schemas.
Related Documentation
- Global Schemas - Request/response envelope structure
- Asynchronous Processing - Handling async operation failures
- API Connection - Authentication and rate limiting
Return to API Specifications