Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt

Use this file to discover all available pages before exploring further.

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.