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

# Authentication

> How to authenticate to the Agnes API: bearer keys, rotation, scopes, and pinning the API contract version.

The Agnes API uses a single auth scheme — **HTTP bearer tokens** — backed
by per-tenant API keys. Auth0 JWTs are accepted on a small set of admin and
user endpoints (Auth0 web app sessions only); programmatic clients should
always use API keys.

<Snippet file="api-base.mdx" />

## Bearer header

<Snippet file="auth-bearer.mdx" />

For example:

```bash theme={null}
curl -X POST https://api.lasscyber.com/api/v1/analyze/ \
  -H "Authorization: Bearer ak_live_b94f…f12c" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "hello", "policy_slug": "default-inbound"}'
```

## Live versus sandbox keys

| Prefix                | Bills? | Calls upstream models?                   | Use for                                                      |
| --------------------- | ------ | ---------------------------------------- | ------------------------------------------------------------ |
| `ak_…` (live)         | Yes    | Yes                                      | Production traffic. Counts against your monthly token quota. |
| `ak_test_…` (sandbox) | No     | No, returns deterministic canned results | Tests, CI, example apps, MCP integration scaffolding.        |

Sandbox keys behave exactly like live keys on every public endpoint — same
schemas, same headers, same idempotency semantics — but every analyzer
output comes from a deterministic stub. See [Sandbox mode](/testing/sandbox-mode)
for the canned-response matrix.

## Minting and rotating keys

Keys are minted from the dashboard at
[`agnes.lasscyber.com/keys`](https://agnes.lasscyber.com/keys) or via the
API (see the [API keys reference](/administration/api-keys)).

The full key value is shown **once at creation** and is never available
again — Agnes only stores a salted hash. To rotate:

1. Mint a new key with the same description / scopes.
2. Roll the new key out to your callers.
3. Delete the old key once traffic has fully drained.

There is no "rotate in place" endpoint; the create-then-delete flow is the
rotation. The audit log records every create / delete event with the actor
and tenant.

## Scopes

Each key carries a list of scopes that limit what it can do. The dashboard
key creator exposes the full canonical list; the most common bundles are:

* **Analyzer-only** — `analyzer:run`, the per-analyzer `*:analyze` scopes,
  and `analyzer_logs:read`. Suitable for production guard keys that only
  call `POST /api/v1/analyze/` and read their own logs.
* **Read-only** — `*:read` scopes only. Suitable for dashboards and
  one-off audit scripts.
* **Admin** — adds policy / YARA / SDP / safety-policy CRUD and
  `api_key:*`. Mint sparingly; prefer Auth0-based admin via the dashboard.

See [Roles & permissions](/administration/roles-and-permissions) for the
mapping from human roles to scopes.

## Tenant resolution

Agnes is multi-tenant. The tenant for a request is resolved automatically:

* **API key** — the key's owning tenant is used. You cannot send an
  `X-Tenant-ID` header to override this; the API rejects mismatches.
* **Auth0 JWT** (web dashboard) — the tenant comes from the user's
  current selection in the tenant switcher.

This means a single API key can only ever act on its own tenant's data. If
your platform serves many end-customers, mint one key per customer.

## Pinning the API contract version

The Agnes API is versioned by **date** (today: `2026-04-16`). The version
is metadata on the OpenAPI document, not on the URL. Pin it on each
request to decouple your client from server evolution:

<CodeGroup>
  ```python Python theme={null}
  from agnes import Agnes
  agnes = Agnes(api_key="ak_live_…", api_version="2026-04-16")
  ```

  ```typescript TypeScript theme={null}
  import { Agnes } from "@lasscyber/agnes-security";
  const agnes = new Agnes({
    apiKey: "ak_live_…",
    apiVersion: "2026-04-16",
  });
  ```

  ```bash cURL theme={null}
  curl -H "Authorization: Bearer ak_live_…" \
       -H "Agnes-Version: 2026-04-16" \
       https://api.lasscyber.com/api/v1/analyze/ …
  ```
</CodeGroup>

The value is sent as the `Agnes-Version` HTTP header. Future minor server
changes will branch on it to keep older clients working. See the
[Versioning policy](/sdks/versioning) for the full lifecycle.

## Email verification

The API requires that the user behind a JWT-authenticated request has a
verified email address. If a JWT request lands without a verified email,
the API returns `403` with `code: "email_not_verified"`. API key requests
are exempt; verification is a one-time human step.

If you hit this, see [`email_not_verified`](/errors/email_not_verified).

## Common pitfalls

* **`X-API-Key` is not supported.** Older docs occasionally reference
  this header; the production API only accepts `Authorization: Bearer`.
* **Do not put keys in client-side code.** Treat API keys like passwords.
  The browser SDK is intentionally bound to dashboard JWTs, not API keys.
* **One key per integration.** It is much easier to revoke a single
  compromised key than to rotate one shared key across many services.

## Next

* [Quickstart](/get-started/quickstart) — your first call end to end.
* [API keys](/administration/api-keys) — programmatic CRUD and the audit
  log.
* [Errors → unauthorized](/errors/unauthorized) — what `401` looks like
  and how to recover.
