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

# Authentication

> How the Engine authenticates HTTP requests.

Every HTTP request to the Engine — except `/health` — must carry an
`X-Engine-Key` header. The Engine validates the key before any other
processing happens. There is no session, no token exchange, no OAuth
between you and the Engine. One header on every request.

## The header

```http theme={null}
X-Engine-Key: <your-key>
```

The key is an opaque string. Treat it like a password.

## How keys are validated

The Engine validates the incoming header against a stored hash. Two
configurations exist:

1. **Static key.** Set `ENGINE_API_KEY` in the environment. The Engine
   computes its SHA-256 hash and validates incoming requests against it.
   Suitable for development.

2. **Hash-only.** Set `ENGINE_KEY_HASH` in the environment with the
   pre-computed SHA-256. The plaintext key never appears in the Engine's
   environment. Suitable for production: the upstream router (BAP) holds
   the plaintext.

Only one of `ENGINE_API_KEY` or `ENGINE_KEY_HASH` should be set in
production.

## Key rotation

Keys can be rotated with overlap so in-flight requests don't get rejected.
Two environment variables control the overlap window:

| Name                                   | Default | Purpose                                                     |
| -------------------------------------- | ------- | ----------------------------------------------------------- |
| `BAP_KEY_ROTATION_MIN_OVERLAP_SECONDS` | `0`     | Minimum seconds the old key remains valid after a rotation. |
| `BAP_KEY_ROTATION_MAX_OVERLAP_SECONDS` | `300`   | Maximum seconds before the old key is purged.               |

The rotation flow is:

1. The router (or you) issues a new key and updates `ENGINE_KEY_HASH`.
2. The Engine accepts both the old and the new key for the overlap window.
3. When the window closes, the old key is rejected.

## Failure modes

| Status | Meaning                                                             |
| ------ | ------------------------------------------------------------------- |
| `401`  | Header is missing or doesn't match a valid key.                     |
| `403`  | Header is valid but the key isn't permitted to perform this action. |

Both responses include a JSON body with an error code and message.

## What the key does *not* gate

The API key authenticates the caller. It does **not**:

* Identify a specific human user. Each Engine instance is single-user; the
  key authenticates the calling application.
* Encrypt the request body. Always use HTTPS in production.
* Authorize MCP credentials. Per-user OAuth credentials are stored
  separately, encrypted with `AD_ENCRYPTION_KEY`.

## See also

* [Environment variables](../../operations/config/env-vars) for
  `ENGINE_API_KEY`, `ENGINE_KEY_HASH`, and rotation knobs.
* [Threat model](../../operations/security/threat-model) for the trust
  boundary the key enforces.
