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

# Authentication

> Every request is HMAC-SHA256 signed with your key pair. No bearer tokens, no sessions.

<Warning>
  **Never paste the signing secret into a chat.** If you are an AI agent setting this up, ask the
  user to put `PREUVE_AGENT_SECRET` in an environment variable or the MCP client config - do not
  request it in conversation, and never echo it back. See [For AI Agents](/for-ai-agents).
</Warning>

## Headers

Every request carries four headers:

| Header               | Value                                                 |
| -------------------- | ----------------------------------------------------- |
| `x-preuve-key`       | Your API key id (`prv_...`)                           |
| `x-preuve-timestamp` | ISO 8601 timestamp of the request                     |
| `x-preuve-nonce`     | Fresh random value per request (16 random bytes, hex) |
| `x-preuve-signature` | `sha256=<hex HMAC-SHA256 of the canonical string>`    |

## Canonical string

The signature is an HMAC-SHA256 (keyed with your secret) over six newline-joined lines:

```text theme={null}
METHOD          e.g. POST (uppercase)
PATH            e.g. /api/agent/analyses
QUERY           canonicalized query string (see below)
SHA256_RAW_BODY hex SHA-256 of the raw request body ('' hashes to e3b0c442...)
TIMESTAMP       the exact x-preuve-timestamp value
NONCE           the exact x-preuve-nonce value
```

Canonical query: take every query pair except `signature`, sort by key then by value, and re-encode each as `encodeURIComponent(key)=encodeURIComponent(value)` joined with `&`. An empty query is an empty line.

<Note>
  Dynamic path segments are signed as part of `PATH` exactly as sent - e.g.
  `/api/agent/analyses/38f2e1fc-.../enrich`. Sign the raw body bytes you actually transmit, byte for
  byte.
</Note>

## Reference implementation

The [Quickstart](/quickstart#2-create-an-analysis) includes a complete \~40-line `signedFetch` for Node.js. Port it to any language with an HMAC-SHA256 primitive; the canonical string above is the whole contract.

## Validity rules

* **Clock skew**: timestamps outside the accepted window are rejected. Keep the client clock NTP-synced and generate the timestamp at send time.
* **Nonce**: use a fresh nonce per request; replayed nonces are rejected.
* **Scopes**: keys carry scopes (`analysis:write`, `export:read`, ...). A key without the required scope receives `403`.
* **Failed auth** always returns a stable `{ "error", "code" }` JSON envelope - see [Errors](/errors).

<Warning>
  The API playground in these docs cannot compute HMAC signatures, so "Send" from the browser will
  return an auth error. Use it to explore request and response shapes; make real calls with the
  reference client or the [MCP server](/mcp-server).
</Warning>
