# Project API keys

Use a project key over HTTPS:

```http
Authorization: Bearer zaun_<64 hexadecimal characters>
```

New keys use the `zaun_` prefix. Existing `zaun_scan_` keys remain valid until revoked.
Ingest keys, which send agent telemetry, use `zaun_ingest_`; see [Ingest keys](#ingest-keys).

Create a key in **API keys** in the console. Select **Run responses and list models**
for completion access (`run:write`). The secret is shown once. Store it in your application's
secret store and load it at runtime. Keep it out of URLs, source control, logs,
and browser code. The plaintext secret cannot be recovered after creation.
Keys can expire or be revoked.

An account created by signup gets its first key automatically: a service key named
**Default key** in the Default project, with `scan:write`, `result:read` and `usage:read`,
and no expiry. It is shown once, on Home right after signup. If you lose it, revoke it and
create another in **API keys**.

Each key belongs to one project and organization. The request cannot change
that scope. Same-project keys with the appropriate permissions can access
retained results and historical usage created by earlier keys, including after
rotation. Other projects' results and usage are not accessible.

## Scopes

| Scope | Operations |
| --- | --- |
| `scan:write` | Prompt, output and document scans. |
| `run:write` | Assessed chat completions and generation model discovery at `GET /v1/models` and `GET /v1/providers/{provider}/models`. |
| `result:read` | `GET /v1/result/{scan_uuid}` for retained project results, `GET /v1/sessions/{session_uuid}`, and every violations route, including one result's redacted text at `GET /v1/violations/{scan_uuid}/evidence`. |
| `result:delete` | `DELETE /v1/result/{scan_uuid}` for retained project results. |
| `usage:read` | `GET /v1/usage`, optionally filtered to a key in the same project. |
| `violation:read` | The violations routes as metadata only: `GET /v1/violations`, its summary, one violation's detail and `GET /v1/sessions/{session_uuid}/timeline`. Not the redacted text. |
| `ingest:write` | OpenTelemetry exports to `/v1/ingest/otlp`, for one telemetry source. Only ingest keys carry it, and they carry nothing else. |

Ask for only the scopes your client needs. A read-only key cannot scan. Public
health and documentation routes need no credential. Customer keys cannot
administer organizations, projects, policy, or other keys.

## Ingest keys

An ingest key lets an agent's OpenTelemetry exporter send telemetry to one source of one
project, such as your Claude Code fleet. It looks like this:

```http
x-zaun-ingest-key: zaun_ingest_<64 hexadecimal characters>
```

Exporters take headers as `name=value` pairs, so use `x-zaun-ingest-key`, which needs no space.
`Authorization: Bearer zaun_ingest_...` works too. The key decides the project and source;
nothing in the telemetry can choose another.

An ingest key has the `ingest:write` scope and no other. It cannot scan, read results or usage,
or administer anything; those requests return `403 forbidden`. Treat it like an error-reporting
key that ships in client configuration: anyone who reads it can send telemetry to that source
and nothing more, and it is cheap to rotate.

To create one:

1. In **Settings**, open **Telemetry** and add a source for the group of agents, with the
   content they will send (prompts and replies, tool names and input, tool output, raw model
   API bodies).
2. In **API keys**, choose **Create key**, then **Send telemetry**, and pick the source.
3. Download the generated configuration while the key is on screen. The key is filled in;
   later downloads from **Telemetry** carry `<ZAUN_INGEST_KEY>` in its place.
4. Start a connectivity check in **Telemetry**, send its code through the agent, and open
   **Health** to see it arrive.

A source can have several active keys, so you can rotate without a gap: create a key for the
same source, update the configuration, then revoke the old key. Refusals of a revoked or
expired key are counted on its source's health report. An organization admin can turn a
project's telemetry off in **Telemetry**; its ingest keys are then refused until it is turned
back on. See [limits and errors](limits.md#telemetry-ingestion-limits) for the size limits and
allowances.

## Keys requested by an agent

When an agent connected to Zaun's MCP server needs a key, it never receives one. It gets a claim
link instead, bound to you, your organization and that agent, valid for ten minutes and usable
once. Collect the key in one of two ways:

- Open the link in the console, signed in as yourself, and select **Reveal key here**. The key
  is created and shown once.
- Run the command the agent gives you in a terminal on your machine:

  ```sh
  npx -y @zaun/cli@0.1.0 claim <claim id or link>
  ```

  It prints a console link and an eight-character code. Open the link, type the code, and the
  key is saved to `~/.config/zaun/api-key`, readable only by you. Only its prefix is printed.
  The console shows the address and program that are waiting for the key. Type the code only if
  you started the command yourself. Your agent should never give you this code; whoever ran the
  command that printed it receives the key.

Only an organization administrator can collect a claim. You can request five claims an hour.
A key collected this way can run scans and nothing else: it carries `scan:write` only, so it
cannot read results or usage. Create a key in **API keys** when you need those scopes. The key
appears in **API keys** like any other service key; revoke it there if it was exposed or
lost.

## Agent access to violations

An agent connected to Zaun's MCP server can read your project's violations without any key. It
uses a short-lived token for you, limited to the scopes you approved and to your current role,
and names the project in `project_id` on every call:

| Scope | Operations |
| --- | --- |
| `zaun:violations:read` | The violations routes as metadata only: `GET /v1/violations`, its summary, one violation's detail and `GET /v1/sessions/{session_uuid}/timeline`, in a project you have a role on. |
| `zaun:evidence:read` | A step-up for `GET /v1/violations/{scan_uuid}/evidence`, used together with `zaun:violations:read`. Only organization admins and Zaun administrators read redacted text this way; developers and viewers are refused. |

Each call checks your access again, so removing you from the project or the organization, or
disabling your account, stops the agent on its next call. Every read is recorded with you, the
agent's client, the project and the route. See [Violations API](violations-api.md#agents-connected-through-the-mcp-server).
The token cannot scan, read results or usage, or stand in for a project key.

## Rotate a key

1. Create a replacement in the console with the required permissions and expiry.
2. Store the replacement and switch every client to it. Both keys work during
   this overlap, provided neither has expired or been revoked.
3. Verify a scan or permitted read with the replacement, then revoke the old key
   in **API keys**. The key ID, not the display prefix, identifies it.
4. Remove the old secret from client configuration. Existing project results
   and historical usage remain available to appropriately scoped replacements.

If a secret is exposed, revoke it immediately in **API keys**. Revocation and expiry
reject subsequent authorization checks. Work already admitted can finish.
Creating replacements does not increase the project or organization rate budget.

## Authentication errors

| Response | What to do |
| --- | --- |
| `401 unauthorized` | Check the key, its expiry, and the organization and project status. |
| `403 forbidden` | Use a key with the required scope. Administration requires a separate organization admin key. On the violations routes, `project_id` must name a project the key or the agent's user can read. |
| `503 reference_not_ready` | Contact support to check classification setup. |
| `503 database_unavailable` | Retry with backoff and honor `Retry-After`. |

## Rate limits and retries

`X-RateLimit-Remaining` reports the organization's remaining request admissions
for the current one-minute window. Token allowances are not reported. It appears on
successful admissions and rate-limit denials. HTTP 429 `rate_limited` includes
`Retry-After`, rounded up to whole seconds. Wait at least that long, then retry
with backoff. Honor `Retry-After` on temporary availability errors too.

The API returns a generated `scan_uuid` for each scan and a `request_uuid` for each completion. Each
HTTP request consumes admission allowance. Do not rotate credentials or create extra keys to bypass
tenant limits. See [limits and errors](limits.md) for budgets and retry outcomes.
