# auth.md — ClearList Authentication (for AI Agents)

ClearList uses a passwordless, agent-friendly model: an emailed one-time code
is exchanged for a long-lived API key. That flow needs no OAuth redirect, no
password and no browser.

Apps that prefer standard OAuth 2.1 (authorization code with PKCE S256) can use
it instead: endpoints are in the authorization server metadata below. OAuth
grants are scoped `read` and/or `write`; a grant without `write` gets
`403` on POST, PUT, PATCH and DELETE. API keys from the email-code flow are
always full access.

## 1. Discover

Machine-readable discovery documents:

- Protected resource metadata (RFC 9728): https://clearlist.me/.well-known/oauth-protected-resource
- Authorization server metadata (RFC 8414, includes `agent_auth` block): https://clearlist.me/.well-known/oauth-authorization-server
- OpenAPI 3.1 spec: https://clearlist.me/openapi.json (also at /.well-known/openapi.json)
- MCP server card: https://clearlist.me/.well-known/mcp/server-card.json

Unauthenticated requests to protected endpoints return 401 with a
`WWW-Authenticate: Bearer resource_metadata="..."` header pointing back to
the protected-resource metadata, so the flow is discoverable from any entry
point.

## 2. Pick a method

| Method | Who it's for | Credential |
|---|---|---|
| API key via email one-time code | Agents, MCP clients, external tools | `X-ClearList-API-Key: cl_<64-hex>` or `Authorization: Bearer cl_<64-hex>` |
| OAuth 2.1 (authorization code + PKCE S256) | Apps a seller connects, e.g. an AI assistant connector | `Authorization: Bearer cl_<64-hex>` access token, scoped `read` / `write` |
| Firebase ID token | ClearList web UI / PWA only | `Authorization: Bearer <firebase_id_token>` |

Agents without an OAuth client should use the API key method: one identity type
(email), one credential (the API key), and no client registration beyond the
email verification below. OAuth clients register dynamically or with a client
ID metadata document; see the authorization server metadata.

## 3. Register

Send a 6-digit verification code to the seller's email. This step also
creates the account if it doesn't exist yet — registration and login are the
same flow.

```http
POST /api/auth/send-code HTTP/1.1
Host: clearlist.me
Content-Type: application/json

{ "email": "seller@example.com" }
```

Response: `{ "success": true }`. The code expires in 10 minutes.

## 4. Claim

Ask the user for the code they received, then exchange it for an API key.
`"agent": true` tells ClearList to mint a long-lived API key instead of a
web session.

```http
POST /api/auth/verify-code HTTP/1.1
Host: clearlist.me
Content-Type: application/json

{ "email": "seller@example.com", "code": "482019", "agent": true }
```

Response: `{ "success": true, "apiKey": "cl_<64-hex>" }`.

The raw key is returned exactly once — store it securely. Keys are SHA-256
hashed at rest. Additional keys (max 5 per account) can be minted later with
`POST /api/auth/api-keys` using an existing key. A key minted that way
inherits the expiry of the key that created it, so it never outlives its
parent; re-run steps 3-4 for a fresh full-length credential.

## 5. Use credential

Send the key on every request:

```http
GET /api/items HTTP/1.1
Host: clearlist.me
X-ClearList-API-Key: cl_<64-hex>
```

Key facts:

- Header: `X-ClearList-API-Key`, or `Authorization: Bearer cl_<64-hex>` (the same credential; OAuth access tokens are sent this way)
- Key format: `cl_` followed by 64 hex characters
- API key expiry is tied to tier (free / Move Sale = 30 days, Garage Sale = 60 days). OAuth access tokens last 1 hour and are renewed with the refresh token. Either returns HTTP 401 once expired.
- Write endpoints accept an `Idempotency-Key` header so retries never create duplicates — see https://clearlist.me/docs/api#idempotency

## 6. Errors

- `401 Unauthorized` — missing, invalid, revoked, or expired key. The response carries `WWW-Authenticate: Bearer resource_metadata="https://clearlist.me/.well-known/oauth-protected-resource", error="invalid_token"`. Re-run steps 3-4 to get a fresh key; an OAuth client uses its refresh token instead.
- `400 Bad Request` on verify-code — wrong or expired 6-digit code. Ask the user to re-check, or re-send with step 3.
- `429 Too Many Requests` — rate limited. Honor `Retry-After` / `X-RateLimit-*` response headers.
- All errors are JSON: `{ "success": false, "error": "<human-readable>", "code": "<machine-readable, when applicable>" }`.

## 7. Revocation

List, mint, and revoke keys with an authenticated call:

```http
DELETE /api/auth/api-keys?id=<key_id> HTTP/1.1
Host: clearlist.me
X-ClearList-API-Key: cl_<64-hex>
```

The `id` query parameter is the `key_id` returned by `GET /api/auth/api-keys`.

- `GET /api/auth/api-keys` — list keys (name + last 4 chars, never the full key)
- `POST /api/auth/api-keys` — mint an additional key (max 5 per account), expiring no later than the key that created it
- `DELETE /api/auth/api-keys` — revoke a key immediately

These three endpoints accept an API key, not an OAuth access token. An app
connected through the OAuth flow gets `403` on mint and revoke: a grant the
seller can disconnect must not be able to leave a credential behind that
outlives it. OAuth tokens are revoked at `POST /api/oauth/revoke` instead.

A seller can also end an OAuth grant themselves, from Settings, without holding
the token: `DELETE /api/auth/connected-apps?client_id=<id>`. A connected app
gets `403` there too, so one app can neither enumerate nor revoke another.

---

Full docs: https://clearlist.me/developers#clearlist-authentication
