# auth.md — how agents get access to coil.trade

Coil is a scored US-equity market board (S&P 500, Nasdaq-100, macro + BTC/ETH books).
This file documents how programmatic access **actually** works, in the spirit of
[auth.md](https://github.com/workos/auth.md): step-wise, copy-pasteable, honest.

## Current state (read this first)

**There are no user accounts and no interactive login.** There IS a minimal, real
OAuth 2.0 service for OAuth/MCP-native clients: `POST /oauth/token` exchanges a Coil
Scanner license key (`grant_type=client_credentials`, key as `client_secret`) for a
1-hour bearer JWT the API accepts. Discovery lives at
`/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`;
`/.well-known/openid-configuration` stays absent because there is no OIDC identity
layer — no login means nothing to assert about who you are, only what your key unlocks.
Access is anonymous, keyed, paid per-request, or OAuth-bearer:

| Method | Credential type | Cost | Header | Gets you |
|---|---|---|---|---|
| Anonymous | none | free | none | status, change-detection state, one full committed sample day, signal audit, proof chain, engine performance, symbol directory |
| Free API key | `api_key` | free (email registration) | `X-Coil-Key` | 25 reads/day across regime, indices, movers, crypto gate, crypto night, tradfi-risk, hold state |
| Wallet sign-in (SIWX) | signed message (no funds move) | free | `SIGN-IN-WITH-X` | the same 25-reads/day tier as the free key, no email — see Step 3b |
| x402 payment | none (signed payment) | $0.001–$0.25/read (USDC on Base, Solana or Algorand) | `X-PAYMENT` | any paid board endpoint, no key, no signup |
| Scanner license key | `api_key` (purchased) | $12/mo ([coil.trade/scanner](https://coil.trade/scanner)) | `X-License-Key` | live intraday board on the HTTP API, no per-day cap (HTTP only — inside MCP the Scanner tier is per-call x402 via the `*_live` tools) |
| Coil Live license key | `api_key` (purchased) | $29/mo or $249/yr ([coil.trade/live-feed](https://coil.trade/live-feed)) | `X-License-Key` | the engine-grade feed: `/api/live/feed`, `/api/live/rules`, `/api/live/setup` over HTTPS, and the same tier over MCP at `https://coil.trade/mcp` (`get_engine_feed`, `get_live_rules`, `get_live_setup`) — structural stops, targets, conviction, lanes and a served `candidates[]`, refreshed every 5–9 minutes in market hours |
| OAuth bearer | `client_credentials` → JWT | included with the license key | `Authorization: Bearer` | same live tier as the license key, 1-hour tokens |

Anonymous access is fully supported — an agent can verify everything worth verifying before
holding any credential at all.

## Step 1 — Discover

Machine-readable surfaces, all free, no auth:

```http
GET /.well-known/api-catalog        # RFC 9727 linkset → the API + its docs
GET /openapi.json                   # full OpenAPI document
GET /llms.txt                       # site map for agents
GET /api/board/status               # is the board fresh right now?
GET /.well-known/mcp/server-card.json   # MCP server card (server at /mcp)
```

## Step 2 — Anonymous reads (no registration of any kind)

```http
GET https://coil.trade/api/board/state          # state hashes — poll this, buy only on change
GET https://coil.trade/api/board/asof?sample=1  # a complete committed day of scores, free
GET https://coil.trade/api/board/signal-audit   # forward-return audit of our own scores
GET https://coil.trade/api/board/proof          # append-only sha256 pre-commitments
GET https://coil.trade/api/perf                 # engine vs SPY/QQQ, percentages only
```

## Step 3 — Register for a free API key (one POST, no password, no OAuth)

Registration endpoint: `POST https://coil.trade/api/key`. Agents may register directly —
the only input is an email address, and the credential comes back in the same response.

```http
POST https://coil.trade/api/key
Content-Type: application/json

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

Response: `{"key": "coilfree_…", "daily_limit": 25, "usage": "send it as the X-Coil-Key header", …}`.
The address is stored only as a salted hash (de-duplication and abuse control). Keys expire
after 90 days of inactivity; re-request with the same email for a fresh one.

```http
GET https://coil.trade/api/board/regime
X-Coil-Key: coilfree_…
```

## Step 3b — Wallet sign-in (SIWX): the same free tier, no email

Have a wallet but no email? Any 402 response declares the `sign-in-with-x` extension
(CAIP-122). Sign the message it describes (EVM `personal_sign`, Solana `signMessage`,
or Algorand ed25519 — the signature proves address control only, **no funds move**) and
send it base64-encoded as the `SIGN-IN-WITH-X` header on the same request. Guided flow:

```http
GET https://coil.trade/api/auth/nonce?address=0xYOU&chain=base   # → message to sign
POST https://coil.trade/api/auth/verify                          # → coilwallet_ key
```

One signed header is a bearer session: replay it as-is on every free-tier endpoint until
its `expirationTime` (issuedAt + 10 minutes), then re-sign from any fresh 402. Quota is
the same 25 served reads/day per address (`tier.used_today` rides every 200).

## Step 4 — Pay per request with x402 (agent-native, no key at all)

Any paid endpoint answers `402 Payment Required` with a `PAYMENT-REQUIRED` header (v2,
CAIP-2) and a v1-dialect body — both the `x402-fetch` and `@x402/*` client lines work
out of the box, on Base (USDC), Solana (USDC) or Algorand (USDCa, fees sponsored — zero
ALGO needed). Prices run $0.001–$0.25 per read.

```http
GET https://coil.trade/api/board/buylist   # → 402 with payment offers; pay, retry, read
```

Full catalogue in the 402 body at `GET /api`, dialect notes at
[coil.trade/learn/x402-dialects](https://coil.trade/learn/x402-dialects).

## Step 5 — License key (the paid tier)

Buy on Gumroad via [coil.trade/scanner](https://coil.trade/scanner) ($12/mo). The license
key is delivered by Gumroad at purchase — there is no claim ceremony and no token exchange.
Send it as the `X-License-Key` header on any paid `/api/board/*` endpoint — the same URLs
that answer 402s serve the live read to a licensed key instead:

```http
GET https://coil.trade/api/board/regime
X-License-Key: XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX
```

(Inside MCP at `https://coil.trade/mcp` the Scanner key does not apply — the MCP live
tier is the **Coil Live** key or per-call x402 via the `*_live` tools; see Step 6's note.)

## Step 6 — OAuth 2.0 for MCP/OAuth-native clients

The [MCP authorization spec](https://modelcontextprotocol.io/) is OAuth, so clients that
only speak OAuth can trade the license key for a standard bearer instead of sending a
custom header. One grant, no registration, no browser:

```http
POST https://coil.trade/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=my-agent&client_secret=YOUR-LICENSE-KEY
```

Response: `{"access_token": "<ES256 JWT>", "token_type": "Bearer", "expires_in": 3600,
"scope": "board:live"}` — then `Authorization: Bearer <token>` on any licensed HTTP board
endpoint. (The MCP server reads the **Coil Live** license key on `X-License-Key`; it does not read
bearers, and it does not read the Scanner license key — the Scanner's live tier inside MCP
is pay-per-call x402 via the `*_live` tools.) Tokens are verified against `/oauth/jwks`. `/oauth/authorize` exists and
honestly refuses: there are no interactive grants to perform. Free `coilfree_` keys are
not exchangeable for tokens — they stay on `X-Coil-Key` (their daily quota rides that
header).

## Errors

| Code | Where | What to do |
|---|---|---|
| 402 | paid endpoints without payment/key | Pay via x402 (offers are in the response), or send a valid `X-License-Key` |
| 403 | invalid/expired license or free key | Re-check the key; free keys expire after 90 days of inactivity — re-request at `/api/key` |
| 429 | free-key daily cap (25) or burst traffic | Back off; upgrade to the license key for uncapped reads |
| 5xx | our side | Retry with backoff; check `GET /api/board/status` |

## Revocation

- **Free keys**: lapse automatically after 90 days of inactivity. To rotate, POST `/api/key` again.
- **License keys**: cancel the Gumroad subscription — the key stops verifying at period end.
- **x402**: nothing to revoke; each request is paid and done.

## Ground rules

Scores and states only — never price targets, position sizes, or advice. Research, not
investment advice. Rate limits are enforced; pace sequentially. Questions:
[coil.trade/contact](https://coil.trade/contact).
