Skip to main content

The golden rule

Never crash on an API error. Every integration should gracefully degrade when Arcus is unavailable, rate-limited, or returns a validation error.

Error classification

Before writing retry logic, classify errors by whether they are safe to retry:
ClassExamplesRetry?
Transient500, 502, 503, 504, network timeoutYes, with backoff
Rate limited429Yes, after Retry-After seconds
Client error400, 401, 403, 404, 409, 422No — fix the request first
Retrying a 422 Unprocessable Entity is wasted effort; the request will always fail until you fix the payload.

Exponential backoff

For transient errors and rate limits, use exponential backoff with jitter:
async function withRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const isRetryable = err.status >= 500 || err.status === 429;
      if (!isRetryable || attempt === maxRetries) throw err;

      const retryAfter = err.status === 429
        ? parseInt(err.headers?.['retry-after'] ?? '1', 10) * 1000
        : Math.pow(2, attempt) * 1000 + Math.random() * 1000; // jitter

      await new Promise((resolve) => setTimeout(resolve, retryAfter));
    }
  }
}

Idempotency on retry

Always use Idempotency-Key when retrying POST/PATCH/DELETE requests. Generate the key before the first attempt and reuse the same key on every retry:
const idempotencyKey = crypto.randomUUID();

const order = await withRetry(() =>
  arcus.orders.create(params, { idempotencyKey })
);
If Arcus received and processed the original request but your network timed out, the retry returns the original response without creating a duplicate.

Handling specific errors

401 Unauthorized

Your API key is missing, invalid, or expired. Check:
  • Authorization header is present and starts with Bearer
  • Key has not been deleted in Settings > Developers
  • Key is for the correct mode (test vs live) for the environment you are calling

403 insufficient_scope

Your key lacks a required scope. The error includes required: <scope>:
{ "error": "insufficient_scope", "required": "orders:write" }
Add the scope to your key in Settings > Developers, or create a new key with the correct scopes.

404 not_found

The resource does not exist or belongs to a different entity. Common causes:
  • Typo in the ID
  • Using a test-mode ID with a live-mode key
  • The record was deleted

409 conflict

State conflict. Common causes:
  • idempotency_key_mismatch: same key reused with a different body — generate a new key
  • resource_in_use: cannot delete a resource that is referenced elsewhere — see the references field in the error body for what is blocking the delete

422 Unprocessable Entity

Validation failure. The param field identifies which field failed and hint explains why:
{
  "error": "invalid_field_value",
  "param": "line_items[0].quantity",
  "hint": "Quantity must be greater than 0."
}

Structured error logging

Log enough context to debug without logging sensitive data:
try {
  const order = await arcus.orders.create(params);
} catch (err) {
  if (err instanceof ArcusAPIError) {
    logger.error('Arcus API error', {
      code: err.code,           // machine-readable; safe to log
      hint: err.hint,           // human-readable; safe to log
      status: err.status,       // HTTP status
      requestId: err.requestId, // include in support tickets
      param: err.param,         // which field failed (422 only)
      // DO NOT log: apiKey, payload body (may contain PII)
    });
  }
}

Circuit breaker

For high-volume integrations, add a circuit breaker to avoid hammering a degraded API:
import CircuitBreaker from 'opossum';

const breaker = new CircuitBreaker(
  (params) => arcus.orders.create(params),
  {
    timeout: 5000,          // trip if request takes >5s
    errorThresholdPercentage: 50, // trip if >50% of requests fail
    resetTimeout: 30000,    // try again after 30s
  },
);

breaker.fallback(() => ({ error: 'service_unavailable', queued: true }));

Monitoring

Set up alerts on:
  • 5xx error rate spike (Arcus outage)
  • 429 rate rising (approaching rate limit; add backpressure or request a limit increase)
  • 401/403 errors (key expiry or accidental deletion)
Include request_id in your alert payloads so on-call engineers can share it with Arcus support.