# Authentication

Norano's API uses **scoped, revocable API keys**, fully decoupled from the browser
(Clerk) session. Send the key as a bearer token on every request:

```http
Authorization: Bearer norano_sk_live_…
```

## Key format

- `norano_sk_live_<rand>` — a live secret key.
- `norano_sk_test_<rand>` — a test-mode secret key (see below).

Keys are hashed at rest; the plaintext is shown **once** at creation and never
retrievable. The display form is `prefix + last4` (e.g. `norano_sk_live_…a1b2`).
Lifecycle: issue, list, rotate (with an overlap window), revoke (immediate).

## Scopes (least privilege)

A key carries only the scopes it needs. The gateway and the MCP server enforce
them per route/tool — a read-only key literally cannot mutate or spend.

| Scope | Grants |
|-------|--------|
| `projects:read` / `projects:write` | read / create projects |
| `plans:read` / `plans:write` | read / upload + parse plans |
| `visualizations:write` | generate visualizations |
| `tours:write` | generate tours |
| `assets:read` | read assets + signed URLs |
| `jobs:read` | read job status |
| `webhooks:read` / `webhooks:write` | read / manage webhook endpoints + delivery log |
| `usage:read` | read per-key usage |

A read-only key holds only the `*:read` scopes + `jobs:read`.

## Test mode

`norano_sk_test_` keys exercise the **full contract** — job lifecycle, signed asset
URLs, and signed webhooks — but the worker short-circuits paid providers and returns
fixture assets. Test jobs transition `queued → running → succeeded` on a timer with
`cost_usd_cents = 0`. Test and live data are isolated: a live key reading a test
resource gets `403 test_mode_resource_in_live`. Rehearse end to end at zero spend
before your first real generation.

## Discovery on 401

Unauthenticated requests get a `401` with the actionable error envelope (see
/docs/api/errors) and a `WWW-Authenticate` header. The server also serves
`/.well-known/oauth-protected-resource`, so OAuth-capable hosts can auto-discover the
auth scheme. API keys are Phase 1; OAuth 2.1 is the additive forward path and keys
keep working.

## Safety for autonomous spenders

- **`dry_run`** — `POST /api/v1/visualizations { …, "dry_run": true }` returns an
  `estimated_cost_usd_cents` with no enqueue and no spend.
- **Per-key spend caps** — a key can carry `monthly_cap_usd_cents` /
  `total_cap_usd_cents`; exceeding it returns `402 budget_exceeded`.
