API Reference

Errors

Every error returns a JSON body with the right HTTP status code. There are two body shapes — one for the retrieve, documents, and usage endpoints, and a leaner financialdatasets-compatible shape for the financial data endpoints.

Error envelopes#

The API uses two error shapes, depending on the endpoint. The retrieve, documents, and usage endpoints return the standard FocusAlpha envelope: success is always false, and the error object carries the status code, a human-readable message, the error name, the request path, a timestamp, and — when an X-Request-ID header was supplied or generated — an optional requestId you can quote when contacting support.

standard-error.jsonjson
{
  "success": false,
  "error": {
    "statusCode": 401,
    "message": "Invalid or missing API key",
    "error": "Unauthorized",
    "path": "/v1/retrieve",
    "timestamp": "2026-05-30T12:00:00.000Z",
    "requestId": "req_8f3c..."
  }
}

The financial data endpoints — Financial Statements, Financial Metrics, SEC Filings, Filing Items, Company Facts, and Institutional Holdings — return a leaner, financialdatasets-compatible body with only two fields: error (the HTTP reason phrase) and message (the human-readable string). There is no success, statusCode, path, or timestamp on these responses — the HTTP status line carries the code.

data-error.jsonjson
{
  "error": "Unauthorized",
  "message": "Invalid API key provided"
}

Status codes#

400Bad Requestoptional
The request body failed validation — a missing or over-long query, an out-of-range top_k, an unknown field, or a malformed filter. Note that tickers, year, quarter, and source_types must be nested under a filters object, not sent as top-level fields. The message names the offending field.
401Unauthorizedoptional
The API key is missing, malformed, unknown, or revoked. See Authentication.
404Not Foundoptional
The requested resource doesn’t exist — for example a document id that isn’t in the corpus. Don’t retry without changing the id.
402Payment Requiredoptional
On the financial data endpoints (financials, filings, financial metrics, company facts, institutional holdings, filing items), you breached either your per-minute rate limit or your monthly quota. A rate-limit breach is transient — back off and retry. A quota or plan cap requires upgrading; don’t blind-retry. See Rate Limits & Quotas.
429Too Many Requestsoptional
On the retrieve endpoint, you exceeded either your per-minute rate limit or your monthly quota. Back off and retry. See Rate Limits & Quotas. (The financial data endpoints return 402 for the same condition.)
500Internal Server Erroroptional
Something failed on our side (including an upstream retrieval or database failure). The request was not billed. Safe to retry with backoff.
503Service Unavailableoptional
Transient backpressure on the financial data endpoints — for example the SEC/EDGAR fetch budget is momentarily exhausted, or a backing store read missed. The response includes a Retry-After header (in seconds); wait that long, then retry.
Why a 500 and not a 401 on outages
Authentication fails closed: if we can’t verify your key because of an internal/database problem, you get a 500, never a misleading 401. A 401 always means the key itself was rejected.

Handling errors#

Check the HTTP status first, then read the error message for specifics. Retry 429, 503, and 500 with exponential backoff; when a Retry-After header is present (on 503), honor it instead of your own backoff. On the financial data endpoints a 402 from a per-minute rate-limit breach is also transient and safe to retry — but a 402 indicating a hard plan or quota cap should not be blind-retried. Don’t retry 400 or 401 without changing the request or key.

Two envelope shapes
The standard envelope nests fields under error (so error.statusCode / error.message); the data endpoints return a flat { error, message }, where message is the human string. Read the HTTP status code from res.status rather than the body so the same handler works for both.
handle.tsts
const res = await fetch(url, options);

if (!res.ok) {
  const body = await res.json();
  // A 402 on a data endpoint is a rate-limit breach when Retry-After is set;
  // a hard plan/quota cap has no Retry-After and should not be blind-retried.
  const retryable =
    res.status === 429 ||
    res.status >= 500 ||
    (res.status === 402 && res.headers.has("retry-after"));

  if (retryable) {
    const retryAfter = Number(res.headers.get("retry-after")) || 0;
    // honor Retry-After (seconds) when present, else exponential backoff
    await backoffAndRetry(retryAfter);
  } else {
    // 400 / 401 / hard 402 — fix the request, key, or plan
    throw new Error(body.message);
  }
}