# Errors

Arche returns machine-readable JSON errors so failed requests can be retried, routed, or escalated programmatically.

## Error envelope

Representative error response:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "http_status": 400,
    "message": "from must be <= to",
    "details": {},
    "trace_id": "req-123"
  }
}
```

Field meanings:

- `code`: stable machine-readable classification
- `http_status`: HTTP status that should drive retry or fallback behavior
- `message`: operator-facing detail for logs and debugging
- `details`: structured context for the error, an empty object when there is none
- `trace_id`: Arche-generated request identifier to include when escalating an issue

Every error carries all five keys. `details` and `trace_id` are present even
when empty or null, so one parser handles every error body.

If you sent an `X-Request-ID` header, Arche echoes it back on the response, so a
failed call has two identifiers: yours and ours. See
[Request IDs](https://docs.arche.fi/troubleshooting/request-ids) for how to set them and which to
quote when escalating.

## Status codes

The public schema documents route-level error statuses including:

- `400` for invalid parameters or malformed requests, including a query
  parameter the route does not accept
- `401` for missing or invalid authentication
- `403` for unauthorized access, and for anything your plan does not include
- `404` when a requested resource cannot be found
- `429` when the request budget is exceeded

## Authentication errors

A `401` carries one of four codes, and each points to a different fix:

- `MISSING_CREDENTIALS` (401): The request carried neither an `X-Api-Key` header nor a bearer token.
- `MISSING_API_KEY` (401): The `X-Api-Key` header was sent but empty.
- `INVALID_API_KEY` (401): The key is not one Arche issued. Every Arche key starts with `ak_`, so a
  value without that prefix, such as a key from another service, returns this
  code. So does a well-formed key that does not exist.
- `API_KEY_REVOKED` (401): The key existed but has been revoked. Create a new key in the developer
  portal.

None of these clear on retry. See [Authentication](https://docs.arche.fi/authentication).

## Plan limits return 403

A capability your plan does not include returns `403` with the code
`FEATURE_NOT_AVAILABLE`, not an empty result. The `details` name what is
required and what you hold:

```json
{
  "error": {
    "code": "FEATURE_NOT_AVAILABLE",
    "http_status": 403,
    "message": "This feature requires the growth plan or higher.",
    "details": { "required_tier": "growth", "current_tier": "developer" },
    "trace_id": "…"
  }
}
```

An endpoint your plan does not include, such as bulk export below Scale,
returns this code.

On the Free plan, a request for a company beyond the 25 you have chosen returns
`403` with a different code, `COVERAGE_SELECTION_FULL`, because releasing a slot
fixes it as well as upgrading does. See
[Choosing companies on Free](https://docs.arche.fi/coverage#choosing-companies-on-free).

Treat it as a configuration outcome, not a transient failure. Retrying will not
change it, and an empty-result fallback will silently hide it. Branch on
`details.required_tier` if you need to surface an upgrade path. See
[Rate limits](https://docs.arche.fi/rate-limits) for what each plan includes.

## Absent is not failed

A request that finds nothing is answered, not failed. On the restatement
timeline, three outcomes are easy to confuse:

- **The identity has versions and no metric changed.** `200`, `by_metric` empty,
  `timeline_severity` `NONE`. `total_hops` counts the versions traversed, so it
  reflects how many versions exist, not how many changes were found. Apple's
  FY2023 income statement returns `total_hops: 2` with `by_metric: {}` — two
  versions that differ in no metric. Read `by_metric` and
  `timeline_severity` to decide whether anything changed, never `total_hops`.
- **The identity has no versions, but the company does.** `200` with
  `total_hops: 0` and everything else empty. Asking Apple for fiscal year 1995
  answers this way. A period the filer never reported is ordinary history, not a
  gap, and it is **not** a `404`.
- **The company has no statements at all.** `404`
  `EDGAR_STATEMENTS_NOT_FOUND`.

So a `404` on this route means "no such filer in the corpus", and an empty `200`
means "nothing to report for that period". Branching on `404` to detect missing
history will read an unreported period as an unrestated one.

`/v1/edgar/statements/restatements/ledger` draws the line differently: it needs
two filings to build a ledger and returns `404 EDGAR_STATEMENTS_NOT_FOUND` when
the period has fewer, where the timeline returns an empty `200`.

An unrecognized `statement_type` is a caller bug and returns `400
VALIDATION_ERROR`, not an empty result.

Neither absent case is a server fault, and neither should be retried. Earlier releases
reported the first case as `502`, which made ordinary reporting history look
like an upstream outage.

## Why a narrative is missing

`GET /v1/ai/companies/{company_id}/narratives` answers `404` with one of three
codes. Each one tells you what to do next:

- `NARRATIVE_AS_OF_UNAVAILABLE`: Arche has no figures for the company as of
  that date. Try a later `as_of`.
- `NARRATIVE_INSUFFICIENT_HISTORY`: the company has fewer than two comparable
  periods. A quarter is compared only with an earlier quarter, and a year with
  an earlier year.
- `NARRATIVE_METRICS_UNAVAILABLE`: none of the metrics the narrative cites is
  reported in both periods. Another `narrative_type` or `statement_type` may
  still have one.

None of the three is a fault, and retrying will not change the answer.

## Troubleshooting

- Log the request path, query parameters, response status, `trace_id`, and the `X-Request-ID` you sent.
- Treat `400` responses as caller bugs to be fixed before retry.
- Treat `429` responses as backoff signals rather than permanent failures.
- Preserve the original request inputs when debugging point-in-time discrepancies.
- Follow [Request IDs](https://docs.arche.fi/troubleshooting/request-ids) when you need to escalate.

> **Warning:** Do not parse only the HTTP status. Use the structured error body so different failure classes can
> be handled explicitly.

## Next steps

- [API reference](https://docs.arche.fi/reference): Browse the live OpenAPI contract rendered from the public schema.
- [Request IDs](https://docs.arche.fi/troubleshooting/request-ids): Correlate a failed request across your logs, the API response, and the portal.
- [Rate limits](https://docs.arche.fi/rate-limits): Handle request budgets, backoff, and throughput-sensitive integrations.
