Skip to main content
Every non-2xx response from the Foil API returns a structured JSON envelope. Treat error.code as the stable, machine-readable field and error.message as human-facing guidance that may evolve.

Error envelope

HTTP status codes

4xx responses generally indicate the caller needs to change something before retrying; 5xx responses indicate Foil’s side needs to recover. The error.retryable field encodes this directly and is safe to key retry logic off.

Error code taxonomy

Error codes are namespaced with a category prefix. The prefix tells you which layer rejected the request; the suffix tells you specifically what went wrong. Branch your error handling on the category prefix first, the specific code second. A generic “auth failure” path that handles any auth.* code is usually correct, with specific codes adding nuance only where your UX demands it (e.g. handling auth.origin_not_allowed by reminding the developer to add their domain to the key’s allowed origins, or auth.insufficient_scope by pointing at the missing scope). Several codes matter mostly to the browser and mobile SDKs, which handle them for you:
  • batch.admission_limited and session.create_contended are retryable. They include a Retry-After header and details.next_action set to retry.
  • session.document_capacity_reached means the session reached its limit on captured documents. Foil stored nothing for the request, and retrying doesn’t help.
  • billing.plan_limit_reached (402) means an organization on the free plan used all of the plan’s included successful session results for the current UTC month. Test keys aren’t limited. The Collect API returns it when the browser or mobile SDK requests a result.
The browser SDK surfaces its own client-side codes - config.* and runtime.*, plus transport.upgrade_required - documented alongside the SDK. See Browser SDK → Error codes for the full table, including retry semantics and the recommended fallback policy.

Details field

Some error codes include a structured details object with extra context. Validation and list-query errors carry fields and parameter_set, where parameter_set names the group of parameters that failed, such as sessions or fingerprints. Collect API header errors carry header_name, and errors that suggest a recovery step carry next_action.

Field errors (request.validation_failed)

Each fields[] entry carries:
  • name - dot-path to the offending field in the request body (or query)
  • issue - a stable short code describing why the value was rejected
  • expected - an optional human-readable description of what’s valid
  • received - the value the server saw (redacted for sensitive fields)

Next action hints

Some errors include a next_action in details to make retry logic straightforward: For everything else, key your retry logic off error.retryable and the HTTP status - see Retry strategy.

Enumerated fields

When a field only accepts a fixed set of values, the rejected field’s expected lists them.

Rate limiting

Rate-limited responses return 429 Too Many Requests with:
  • Retry-After header - seconds until the next acceptable retry
  • X-RateLimit-Limit - the bucket size for this organization and key type
  • X-RateLimit-Remaining - 0 at the point of rejection
Respect Retry-After. Don’t retry inside the window - further requests count against the limit and typically extend the cooldown. The server SDKs don’t retry for you, so bulk workflows need their own backoff. See Rate limits for the default limits and how the window works.

Using request_id

Every response carries the request ID in the X-Request-Id response header. JSON responses repeat it as meta.request_id on success and as error.request_id on failure. Include it verbatim when you:
  • Email security@usefoil.com about a suspected security issue
  • Open a support ticket about an unexpected error
  • Correlate a client-side report with server-side logs
Foil generates IDs that look like req_0123456789abcdef0123456789abcdef (32 hex characters after the prefix), and each generated ID is unique to one request. To correlate a request with your own trace ID, send an X-Request-Id request header. Foil trims surrounding whitespace and uses the value as the request ID in the X-Request-Id response header, meta.request_id, and error.request_id. Foil doesn’t check the value’s format or uniqueness, so send a value that is unique to each request.

Handling errors in the server SDKs

Each server SDK throws structured error types that mirror the envelope above. The Node SDK’s types illustrate the pattern every SDK follows.
Node.js
Error classes across the SDKs: The API error classes expose equivalent fields - status, code, request_id, field_errors, docs_url, and the raw response body - named to match each language’s conventions, such as RequestID in Go. In PHP, read the code from $error->errorCode.

Retry strategy

A minimal retry loop that handles the common cases:
  1. Read error.retryable. If false, don’t retry - the request will fail the same way every time.
  2. If the response has a Retry-After header, wait at least that many seconds before retrying.
  3. Otherwise, retry with exponential backoff (e.g. 1s, 2s, 4s, 8s, capped at 30s) with jitter.
  4. Cap total retries at 3–5 attempts before surfacing the error to the caller.
  5. Always log error.request_id on the last attempt so support can correlate.
None of the server SDKs retry requests or honor Retry-After, and their error types expose the response body but not the response headers. Wrap SDK calls in your own loop. This Node.js example retries only errors that Foil marks as retryable and backs off on its own schedule:
Node.js
Network failures, such as timeouts and connection resets, aren’t API errors. The SDKs raise them as the underlying HTTP client’s errors, and your loop decides whether to retry them. If you call the REST API with your own HTTP client, use the Retry-After header for 429 responses, as shown in Rate limits.

What’s next

Authentication

Key types, scopes, and lifecycle.

Pagination

Iterating through list endpoints.

API introduction

Surface-level summary of the API.

Troubleshooting

Operational guidance when things go wrong.