> ## 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 bap-engine authenticates products and admins.

Two header types authenticate against bap-engine:

* `X-Platform-Key` — for product calls (most endpoints).
* `X-Admin-Key` — for product registration and policy updates.

`/health` requires neither.

## Platform keys

Products call bap-engine using their `X-Platform-Key`. The key is
issued at registration:

```bash theme={null}
POST /products/register
# returns: { "platform_api_key": "pk-sept-..." }
```

Save the plaintext. The orchestrator only stores the SHA-256 hash;
re-issue isn't possible.

The key is opaque from the orchestrator's perspective — no scopes, no
expiry. It identifies the product. Quota and rate limits live in the
product's policy, not in the key.

### Validation

On every request, the orchestrator:

1. Reads the `X-Platform-Key` header.
2. Computes SHA-256.
3. Looks up `products.api_key_hash`.
4. If no match → `401 INVALID_PLATFORM_KEY`.
5. Otherwise, the product is the calling tenant for the request.

The hash compare uses constant-time comparison (`hmac.compare_digest`).

### Rotation

Today, no built-in rotation endpoint for platform keys. To rotate:

1. Register a new product with a fresh slug, or
2. Manually update `products.api_key_hash` with a new hash, then push
   the new plaintext to your product's secret store.

A future version will add `POST /products/{id}/rotate-platform-key`.

## Admin keys

Two endpoints require `X-Admin-Key` on top of (or instead of) the
platform key:

* `POST /products/register`
* `PUT /products/{product_id}/policy`

The admin key is set via `ORCH_ADMIN_KEY` on the orchestrator.
There's exactly one admin key per orchestrator deployment.

### Validation

Plain string compare via `hmac.compare_digest`. No hash, no
encryption.

### Rotation

Trivial:

1. Generate new value: `python -c 'import secrets;
   print(secrets.token_urlsafe(48))'`.
2. Update `ORCH_ADMIN_KEY` in env.
3. Restart the orchestrator.
4. Update tooling that calls admin endpoints.

## Per-engine keys

The orchestrator generates per-engine API keys
(`sk-sept-<random>`) at provision time. These are separate from
platform keys; they're used by the product to authenticate to the
engine itself, not to the orchestrator.

See [Engine contract](../engine-contract#auth-flow) for how engine
keys flow from orchestrator → product → engine.

## What the keys don't do

* **Identify users.** `user_id` is a string the product picks. The
  orchestrator doesn't authenticate the user upstream of the product.
* **Encrypt traffic.** TLS belongs at your reverse proxy.
* **Carry scopes.** All platform keys can do everything against their
  own product. Scope at the product layer.

## Failure modes

| Status | Code                   | When                                                              |
| ------ | ---------------------- | ----------------------------------------------------------------- |
| `401`  | `INVALID_PLATFORM_KEY` | Header missing or doesn't match a known product.                  |
| `401`  | `INVALID_ADMIN_KEY`    | Admin endpoint called without (or with wrong) `X-Admin-Key`.      |
| `403`  | `POLICY_DENIED`        | Key valid but the action isn't permitted by the product's policy. |

## See also

* [Security](../security) — how keys live at rest.
* [Errors](./errors) — full error catalog.
* [Admin endpoints](./endpoints/admin) — registering products,
  setting policy.
