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:

  1. Verify signing algorithm (Ed25519)
  2. Check private key matches public key
  3. Sign exact payload JSON without modification
  4. Ensure timestamp is recent

“Rate limit exceeded”

Solutions:

  1. Implement client-side rate limiting (stay under 100/min)
  2. Add request queueing
  3. Use exponential backoff
  4. 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:

  1. Check field constraints in spec
  2. Validate data before sending
  3. Use unique import_ids
  4. Keep strings under 255 characters

Async operation stuck in “pending”

Solutions:

  1. Continue polling (normal for long operations)
  2. Check status every 2-5 seconds
  3. Timeout after reasonable period (30-60s)
  4. 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_RECORD has a machine-readable code field
  • Async operations use status field (pending/processing/persisted/failed/rejected)
  • Rate limits include retry_after for proper handling
  • Validation errors describe the specific field issue

For HTTP status-based handling, see Global Schemas.



Return to API Specifications


Back to top

Welcome to the Arkipel DevKit! This documentation will guide you through everything you need to build clients for Arkipel communities.

Contact: devkit@arkipel.co | Page URLs

Copyright © 2026 Arkipel. Distributed under an MIT license.