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

# Authentication

> Session cookies, API keys, scopes, lifecycle, and device authorization.

Every API request authenticates as one of two principals. This page covers both modes, the scope model, and the key lifecycle.

## Two principals

ai-cmo.dev has two mutually exclusive authentication modes.

| Principal           | Transport                                            | Scope                                       |
| ------------------- | ---------------------------------------------------- | ------------------------------------------- |
| **Browser session** | `Cookie: geo.session=<signed-cookie>`                | Full access including session-only actions  |
| **API key**         | `Authorization: Bearer gp_live_<43-base64url-chars>` | Machine scopes only; bound to one workspace |

<CodeGroup>
  ```bash Session (cookie) theme={null}
  curl -H "Cookie: geo.session=..." https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs
  ```

  ```bash API key (bearer) theme={null}
  curl -H "Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
       https://ai-cmo.dev/api/v1/workspaces/{workspace}/runs
  ```
</CodeGroup>

## API key format

A production key starts with `gp_live_` followed by exactly 43 base64url characters:

```text theme={null}
gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

The format is validated by the server. Keys that do not match the pattern are rejected with `401 invalid_key`.

## Scope matrix

| Route group                           | Read scope         | Write scope                                                                                                                                                                                                    |
| ------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Workspace summary                     | `workspace:read`   | Session-only                                                                                                                                                                                                   |
| Settings, profile, model, competitors | `settings:read`    | `settings:write`                                                                                                                                                                                               |
| Connections                           | `connections:read` | `connections:write` (including `POST .../connections/configure` for GA4 and Search Console), `connections:test`. Only `credentials:write` / `credentials:reveal` on the raw credential store are session-only. |
| Prompts                               | `prompts:read`     | `prompts:write`                                                                                                                                                                                                |
| Runs and research                     | `runs:read`        | `runs:write`                                                                                                                                                                                                   |
| SEO processing                        | `metrics:read`     | `runs:write`                                                                                                                                                                                                   |
| Scores, metrics, time series          | `metrics:read`     | None                                                                                                                                                                                                           |
| Recommendations                       | `recs:read`        | `recs:write`                                                                                                                                                                                                   |
| Reports and report catalog            | `reports:read`     | No public write route                                                                                                                                                                                          |
| Page evaluations                      | `reports:read`     | `runs:write`                                                                                                                                                                                                   |
| Chat threads                          | `chat:read`        | `chat:write`                                                                                                                                                                                                   |
| Credit balance and usage              | `credits:read`     | Top-up is session-only                                                                                                                                                                                         |
| Team                                  | `workspace:read`   | Session-only                                                                                                                                                                                                   |

Health and OpenAPI discovery are public (no auth required).

## Session-only actions

These can **never** be performed with an API key:

* Create a workspace
* Create, deliver, rotate, confirm-rotation, acknowledge, or revoke API keys
* Write or reveal stored connector credentials
* Add mock top-up credit
* Backfill AgentMail inboxes
* Invite, change, or remove team members

## Full scope list

API keys carry one or more of these scopes:

```text theme={null}
workspace:read, settings:read, settings:write, connections:read,
connections:write, connections:test, prompts:read, prompts:write,
runs:read, runs:write, recs:read, recs:write, reports:read,
metrics:read, chat:read, chat:write, credits:read
```

If no scopes are specified at creation, the key defaults to `["workspace:read"]`.

## API key lifecycle

```text theme={null}
pending → active → grace → revoked
                ↓
           (rotate)
          pending → active
```

| State     | Meaning                                                                                 |
| --------- | --------------------------------------------------------------------------------------- |
| `pending` | Created, secret not yet delivered                                                       |
| `active`  | Key is live and works                                                                   |
| `grace`   | Previous key, once its replacement delivery was acknowledged (15 minutes, then revoked) |
| `revoked` | Permanently disabled                                                                    |

### Creating a key

Use the dashboard (Workspace → API Keys) or `POST /api/v1/workspaces/{workspace}/api-keys` with a session cookie.

<ParamField name="name" type="string" required>
  Key name.
</ParamField>

<ParamField name="scopes" type="string[]" default="[&#x22;workspace:read&#x22;]" optional>
  Scopes the key carries.
</ParamField>

<ParamField name="allowedCidrs" type="string[]" optional>
  Strict IPv4 or IPv6 CIDR ranges the key may authenticate from.
</ParamField>

<ParamField name="idempotencyKey" type="string" optional>
  Makes the create safe for retries.
</ParamField>

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ci-deploy-key",
    "scopes": ["runs:write", "metrics:read"],
    "allowedCidrs": ["203.0.113.0/24"]
  }'
```

The response contains the secret. The server stores only an HMAC-SHA256 hash of it, so it cannot be recovered from storage — but while the delivery is still pending you can retrieve it again with `resume-delivery` (see below). Once the delivery is acknowledged or its 10-minute window expires, the plaintext is gone for good.

Response:

```json theme={null}
{
  "keyId": "key_abc123",
  "secret": "gp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "prefix": "gp_live_xxxxxxxxxx",
  "last4": "abcd",
  "deliveryId": "del_abc123"
}
```

### Delivery window

The secret is available for **10 minutes** via encrypted delivery. After that, delivery expires and you must create a new key. Use the `acknowledge` or `resume-delivery` actions on `POST /api/v1/workspaces/{workspace}/api-keys/{keyId}` to manage delivery.

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys/{keyId} \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{"action": "resume-delivery", "deliveryId": "10fd331a-..."}'
```

### Acknowledge

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys/{keyId} \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{"action": "acknowledge", "deliveryId": "10fd331a-..."}'
```

Acknowledging marks the key as `active` and clears the encrypted delivery ciphertext.

### Rotating a key

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys/{keyId} \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{"action": "rotate"}'
```

A new key secret is returned as a `pending` replacement. The old key keeps working and only enters `grace` — a 15-minute window before it is revoked — once that replacement's delivery is acknowledged. Call `confirm-rotation` on the new key to revoke the old one immediately.

### Confirm rotation

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys/{keyId} \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{"action": "confirm-rotation"}'
```

This revokes the old key immediately. The replacement must be acknowledged first.

### Revoke

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys/{keyId} \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{"action": "revoke"}'
```

### CIDR restrictions

Keys can be restricted by client IP at creation time. Requests from outside the `allowedCidrs` range are rejected with `403 ip_forbidden`.

```bash theme={null}
curl -X POST https://ai-cmo.dev/api/v1/workspaces/{workspace}/api-keys \
  -H "Cookie: geo.session=..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "restricted-key",
    "allowedCidrs": ["10.0.0.0/8", "203.0.113.0/24"]
  }'
```

### Workspace binding

An API key is bound to exactly one workspace at creation. Using a path segment that differs from the bound workspace returns `404 workspace_not_found`.

## Device authorization

For agents and CLIs, ai-cmo.dev supports device authorization as an alternative to API keys. The `ai-cmo-mcp` package uses this flow: it opens a browser for login, and credentials are cached at `~/.ai-cmo/credentials.json` (mode 0600).

The flow uses `POST /api/v1/device-authorizations` (public), then polling at `GET /api/v1/device-authorizations/{code}` (session), and `POST /api/v1/device-authorizations/{code}/token` (public), which returns a real workspace-bound `apiKey` — not a short-lived token. Clients such as the MCP server persist it as their credential, so it is a way to *provision* an API key without copy-paste, not an alternative to API keys.

## Anti-enumeration

Invalid, absent, unauthorized, and cross-workspace workspace identifiers all return `404 workspace_not_found`. Do not use 404 responses to infer whether a workspace or resource exists.

## Idempotency conventions

| Action                    | Convention                                                        |
| ------------------------- | ----------------------------------------------------------------- |
| Recommendation transition | Required `idempotencyKey` in body                                 |
| Mock top-up               | Required `idempotencyKey` in body                                 |
| Run start                 | Optional `idempotencyKey` in body                                 |
| API key create or rotate  | Optional body key                                                 |
| Chat message              | `Idempotency-Key` header accepted; server generates one if absent |
