# Accounts and API keys

> Tenants, API keys, scopes and console logins. Everything that authenticates you to Zanora, and how to keep it narrow.

## Tenants

Everything you own on Zanora belongs to a **tenant**. The type depends on your role:

| Role | Tenant | Id prefix | Created with |
|---|---|---|---|
| Buyer | **workspace**: the unit of budget and policy, holding agent wallets | `wsp_…` | `POST /v1/signup` with `role: "buyer"` |
| Seller | **provider**: the unit of listings and earnings, with one settlement wallet | `prv_…` | `POST /v1/signup` with `role: "seller"` |

A tenant's id comes **from its credential**. You almost never send `workspaceId` or `providerId` in a request body. If you do, it is ignored for anything except an admin key. This means a leaked request can't be replayed against someone else's tenant.

## API keys

An API key looks like `zk.akey_3652ec9e….<secret>` and goes in the `x-api-key` header on every call:

```bash
curl -s https://api.zanora.dev/v1/whoami -H "x-api-key: $ZANORA_API_KEY"
```

```json
{
  "principal": "workspace(wsp_91c2…)",
  "kind": "workspace",
  "workspaceId": "wsp_91c2…",
  "scopes": ["wallets:read", "wallets:write", "policies:write", "…"],
  "keyId": "akey_3652ec9e…"
}
```

Make `/v1/whoami` your first call with any new key. It tells you whether the key works and what it's allowed to do.

> **Warning — Keys are shown once:**
>
> Zanora stores only `sha256(token)`. A lost key can't be recovered, only replaced: mint a new one from another key, or from the console (**Settings → API keys**) after signing in with your password.

### Your root key and the keys you mint from it

Signup gives you a **root key** with every scope your role can use. Use it to set up, not to run production. Mint a narrower key for each service or agent:

```bash
curl -s -X POST https://api.zanora.dev/v1/auth/keys -H "x-api-key: $ROOT_KEY" \
  -H 'content-type: application/json' \
  -d '{"label":"invoice-agent","scopes":["discovery:read","wallets:read","receipts:read","ratings:write"],"expiresInSeconds":2592000}'
```

The rules:

- **Delegation only narrows.** A new key's scopes must be a subset of the scopes on the key that minted it, and it can't outlive that key.
- **A key's scopes are fixed when it's minted.** If a release adds a new scope, existing keys don't get it. Mint a replacement if you need it.
- **Rotation doesn't need downtime.** `POST /v1/auth/keys/:id/rotate` with `{"graceSeconds":300}` keeps the old secret working for five minutes while you redeploy.
- **Revoking takes effect on the next request.** `DELETE /v1/auth/keys/:id?cascade=true` also revokes every key minted from it. Use that when a key has leaked.

## Scopes

A **scope** decides which routes a key can call. **Ownership** decides which records it can reach. These are separate checks, and you need both. A provider key with `wallets:read` still can't read a buyer's wallet.

| Scope | Buyer root key | Seller root key | Allows |
|---|:-:|:-:|---|
| `discovery:read` | ✓ | ✓ | search the marketplace |
| `capabilities:read` | ✓ | ✓ | read capabilities; sellers list their own |
| `capabilities:write` | | ✓ | publish, edit and deprecate capabilities |
| `providers:read` | ✓ | ✓ | read provider profiles; request verification |
| `workspaces:read` | ✓ | | read your workspace |
| `wallets:read` / `wallets:write` | ✓ | ✓ | read balances and ledgers; create and fund wallets |
| `policies:read` / `policies:write` | ✓ | | read and change spending rules |
| `approvals:read` / `approvals:write` | ✓ | | see held spends; approve or deny them |
| `ratings:write` | ✓ | | rate a seller you bought from |
| `receipts:read` | ✓ | ✓ | read receipts: purchases for buyers, sales for sellers |
| `payments:write` | | ✓ | the x402 routes a seller's middleware calls |
| `payouts:write` | | ✓ | set a payout destination; request payouts |
| `rail:read` | ✓ | ✓ | what funding and payout rails exist; your rail transactions |
| `keys:read` / `keys:write` | ✓ | ✓ | list, mint, rotate and revoke keys |

`GET /v1/auth/scopes` returns the full list. When a key is missing a scope, the `403` names the scope it needed:

```text
403 {"error":{"code":"FORBIDDEN","message":"GET /v1/metrics requires the \"metrics:read\" scope (key ci-publish holds: capabilities:write)"}}
```

> **Tip — Give an agent a key without approvals:write:**
>
> If an agent's key can't record approvals, the model can't approve its own held spend, even if you've set up in-client approval prompts. See [Approvals](https://docs.zanora.dev/buyers/approvals.md).

## Console logins

The [console](https://console.zanora.dev) uses an email and password. Signing in creates a **session**, which is an ordinary API key of kind `session`, held in an httpOnly cookie. It has exactly your tenant's permissions and no more.

- **Resetting a password** revokes every console session but **no** API keys, so your production agents keep working.
- **Signing out** revokes that session only. You can't use it to revoke an agent's API key by accident.
- If you sign up through the API rather than the console, include `password` in the signup request if you want to be able to sign in to the console. The password only takes effect once the email is confirmed.
