> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parmanasystems.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How to authenticate API requests: the bearer key model, what gets checked, and how it differs from Policy evaluation and gateway attestation.

<Info>**\[AVAILABLE]**. `packages/api/src/middleware/caller-auth.ts`, `StaticKeyAuthenticator`, verified against a live server in this session.</Info>

## Send a bearer key

Every route requires `Authorization: Bearer <key>` except `GET /health`, `GET /ready`,
`GET /openapi.yaml`, and `GET /documentation` (liveness/readiness probes and API
documentation, `packages/api/src/app.ts`).

```bash theme={null}
curl http://localhost:3000/version \
  -H "Authorization: Bearer $PARMANA_API_KEY"
```

A missing or invalid header returns 401 before a Business Transaction is even constructed:

```json theme={null}
{ "error": "authentication required" }
```

## Where keys come from

Keys are minted by `scripts/generate-api-key.ts`, one per calling system. The raw key is
shown once and never written to disk; only a SHA-256 hash of it is configured server-side
via `PARMANA_API_KEYS`, and comparisons run in constant time
(`packages/api/src/auth/StaticKeyAuthenticator.ts`). There is no self-service key-management
endpoint: issuing and rotating keys is an operator action, not an API call. See [Deploy
patterns](/guides/deploy-patterns#caller-authentication) for the full setup and rotation
procedure.

## What this layer does and does not prove

Caller authentication answers exactly one question: **should this HTTP request be
entertained at all.** It is the first thing that runs, ahead of Policy evaluation and
[gateway attestation](/concepts/gateway-attestation), and it is independent of both:

* A well-authenticated caller submitting a Policy-rejected transaction is still rejected,
  see [Policies and the decision](/concepts/policies-and-the-decision).
* A well-authenticated caller does not thereby prove anything about *who authorized* the
  underlying business action, that is what [Execution
  Authorization](/concepts/execution-authorization) and [the gateway](/concepts/the-gateway)
  establish, on a completely separate signature.

Keeping these layers apart is deliberate: see [How Parmana
thinks](/how-parmana-thinks).

## Local development

`PARMANA_AUTH_DISABLED=true` skips this middleware entirely and logs a loud warning at
startup every time it does. It exists for local development and running the tutorials.
**Never set it in a real deployment.** It removes the only authentication this API has, and
every route becomes reachable by anyone who can reach the port.

## Rate limiting

<Info>**\[AVAILABLE]**, `packages/api/src/middleware/rate-limit.ts`.</Info>

`POST /execute` — the endpoint that signs and writes to the database on every request — is
rate-limited **per authenticated caller identity** (the `callerId` this page establishes), not
by IP: a design-partner integration commonly calls from a shared backend IP, where an IP-keyed
limit would either starve every caller behind it or be loose enough to mean nothing. It is
mounted only when caller authentication is enabled; there is no caller identity to key off when
auth is disabled. `GET /health` and `GET /ready` get a separate, deliberately more permissive
limit keyed by IP instead, since both are legitimately polled on a fixed interval by PaaS
health-check infrastructure and must never be tight enough to throttle that.

A rejected request gets `429`, `{"error":"Rate limit exceeded. Try again later.","code":
"RATE_LIMITED"}`, and a `Retry-After` header, and never reaches policy evaluation or signing —
no nonce is consumed and nothing is signed for a request this middleware rejects. Both limits
have working defaults sized for a design-partner evaluation deployment, not high-volume
production traffic, and are independently configurable per deployment via
`RATE_LIMIT_EXECUTE_PER_MINUTE` / `RATE_LIMIT_HEALTH_PER_MINUTE` (see `.env.example`).

<Warning>
  **Scope, precisely, the same caveat as the replay-nonce store's** (see [Idempotency and
  nonces](/api-reference/idempotency-and-nonces)). The limiter's store is
  `express-rate-limit`'s default, in-memory, single-process store. A multi-machine deployment
  counts independently per machine — the effective ceiling for a given caller is the configured
  per-minute limit **times** the number of machines serving traffic, not a fleet-wide limit
  enforced once across every machine. A shared store (Redis or equivalent) would close that gap;
  none is wired in today.
</Warning>

## Errors

| Status | Condition                                 | Body                                                                      |
| ------ | ----------------------------------------- | ------------------------------------------------------------------------- |
| 401    | Missing or invalid `Authorization` header | `{"error":"authentication required"}`                                     |
| 429    | Rate limit exceeded (see above)           | `{"error":"Rate limit exceeded. Try again later.","code":"RATE_LIMITED"}` |

See the [Error catalog](/api-reference/error-catalog) for every other error this API returns.
