# auth.md

You are an agent. This file tells you how to register for a PreReason API key on behalf of the person you are working for, when you have no browser session of your own.

PreReason serves financial market briefings on macro, rates, bonds, FX, commodities, listed companies and Bitcoin over REST and MCP. The API is at `https://api.prereason.com`. The page a person approves on is at `https://www.prereason.com`.

## Supported methods

The auth.md convention defines two flows. PreReason supports one of them.

- **User claimed: supported.** You start a claim and receive a code and a token. You show the person one link. They sign in, or create an account on that page, and approve. Your next poll returns the key. The rest of this file describes that flow.
- **Agent verified: not supported.** PreReason does not accept an identity assertion from an agent provider. There is no `ID-JAG` exchange, no `agent_auth` block in any OAuth metadata, and no OAuth token endpoint in this flow. The credential is an API key, not an access token.

No key is issued without an approving person, and every key belongs to the account of the person who approved it.

## Before you start

If the person has already given you a key (the MCP bridge reads it from `PREREASON_API_KEY`), use it and stop here.

Some calls need no key at all. Over MCP, `list_briefings`, `list_metrics` and `get_health` answer anonymous callers.

## Step 1. Start a claim

```http
POST /api/agent/claims HTTP/1.1
Host: api.prereason.com
Content-Type: application/json

{
  "client_name": "Your agent or app name",
  "purpose": "One line on what the key is for"
}
```

The call is anonymous: it needs no account, no key and no identity assertion. Every field is optional. It returns a claim, not a credential. Nothing you can call the API with exists until the person approves.

- `client_name`: 64 characters at most, no URLs. It is shown on the approval page and becomes the key name, `Agent: <client_name>`.
- `purpose`: 200 characters at most. Shown on the approval page.
- `client_version`: 32 characters at most, no URLs.

Response (201):

```json
{
  "claim_code": "PR-7K2M-9QXD",
  "claim_token": "prc_...",
  "status": "pending",
  "approve_url": "https://www.prereason.com/claim/PR-7K2M-9QXD",
  "expires_at": "2026-09-20T21:15:00.000Z",
  "expires_in": 900,
  "poll": {
    "url": "https://api.prereason.com/api/agent/claims/PR-7K2M-9QXD",
    "method": "GET",
    "header": "Authorization: Bearer <claim_token>",
    "interval_seconds": 5
  },
  "instructions": "...",
  "without_a_human": {
    "summary": "...",
    "quickstart": "https://www.prereason.com/docs#pay-per-call"
  },
  "docs": "https://www.prereason.com/docs#agent-access"
}
```

- `claim_code` is the short code a person can read. It is safe to show.
- `claim_token` is yours alone and is returned exactly once. Hold it in memory for the length of the claim. Do not show it to the person and do not log it. PreReason stores only a hash of it.
- `approve_url` is the one link the person needs.
- `without_a_human` is the door for a caller that has nobody to ask. Everything above it assumes a person is watching, and most callers here are not. So the response also names the path that needs no key and no account: a priced short URL quotes a payment to an anonymous caller and serves the data once it is paid. That exchange is x402: the quote arrives as a 402, and the paid retry carries a `PAYMENT-SIGNATURE` header. Its `quickstart` link is the whole procedure. Present only when payment is enabled, because naming a route that would refuse the caller is worse than saying nothing.

## Step 2. Hand the link to the person

Show `approve_url` to the person and let them open it in their own browser. Do not open it yourself and do not act for them. Suggested wording:

> Open this link, sign in or create a PreReason account, and click Approve: `<approve_url>`

On that page the person signs in or creates an account, sees which agent is asking and why, and chooses Approve or Deny. Nothing is created until they click Approve.

A claim lives 15 minutes. The approval page can extend it while the person is on it, never past 60 minutes from when the claim was created, so read `expires_at` on every poll.

## Step 3. Poll for the key

```http
GET /api/agent/claims/PR-7K2M-9QXD HTTP/1.1
Host: api.prereason.com
Authorization: Bearer <claim_token>
```

Poll every 5 seconds. The `status` field tells you what to do next.

| `status` | Meaning | What to do |
| --- | --- | --- |
| `pending` | The person has not answered yet. | Wait `poll_interval` seconds and poll again. |
| `approved` with `api_key` | Approved. This response carries the key, once. | Store `api_key` now and stop polling. |
| `approved` with `error: KEY_ISSUE_FAILED` | Approved, but the key could not be issued just now. | Wait `retry_after` seconds and poll again. |
| `approved` with `error: KEY_LIMIT_REACHED` | Approved, but the account holds as many keys as it may, so no key can be issued until the person revokes one. Polling alone will not clear this. | Stop polling and tell the person what `next` says. Once they have revoked a key, poll again. The approval stands, so they do not approve twice. |
| `delivered` | The key was already handed over for this claim. | If it was lost, start a new claim. |
| `denied` | The person declined. | Ask the person before starting another claim. |
| `expired` | Nobody approved and collected in time. | Start a new claim. |

The approved response, the only one that carries a key:

```json
{
  "status": "approved",
  "claim_code": "PR-7K2M-9QXD",
  "api_key": "pr_live_...",
  "delivered_once": true,
  "key": { "id": "...", "name": "Agent: Your agent or app name", "prefix": "...", "scopes": ["read"] },
  "docs": "https://www.prereason.com/docs#agent-access"
}
```

The same response also carries `account` (the tier and limits of the account that approved) and `how_to_use` (the three ways to send the key, which Step 4 repeats).

The key is created at the moment of this response and is never stored in readable form, so it cannot be shown again. A lost key means a new claim.

An unknown code, a malformed code and a wrong token all answer the same 404 `CLAIM_NOT_FOUND`.

## Step 4. Use the key

Send it as a bearer credential:

```http
GET /api/context?briefing=btc.quick-check HTTP/1.1
Host: api.prereason.com
Authorization: Bearer <api_key>
```

- REST: any endpoint in `https://api.prereason.com/api/openapi/spec.json`.
- MCP over Streamable HTTP: `POST https://api.prereason.com/api/mcp` with the same header.
- MCP bridge: run `npx -y @prereason/mcp` with `PREREASON_API_KEY=<api_key>` in its environment.

The key has the `read` scope and the limits of the approving account. The person can see it, named `Agent: <client_name>`, with their other keys at `https://www.prereason.com/settings`, and can revoke it there. If a claim is approved under a `client_name` that already has an active key on that account, the new key replaces the old one and the old one stops working.

If a key that worked starts answering 401, it was revoked or replaced. Drop it and start again at Step 1.

## From inside an MCP session

If you are connected to `https://api.prereason.com/api/mcp` with no key, that server never hands out a key and never asks for one: `get_context` and `get_metric` answer that a key is needed until one is attached. To get one, make the HTTP calls above, or run the bridge, `npx -y @prereason/mcp`, with no key set: it makes the same calls for you, shows the person one `approve_url`, and saves the key once they approve.

## Limits and errors

| Status | Code | Where | What to do |
| --- | --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Step 1 | A field is too long, is not a string, is not printable text, or is a name that carries a URL. Fix the one named in `field`. |
| 429 | `RATE_LIMITED` | Step 1 | At most 5 claims an hour from one address. Poll the claim you already hold, or wait `retry_after` seconds. |
| 429 | `CLAIM_RATE_LIMITED` | Step 1 | This address has too many claims waiting for approval. Wait `retry_after` seconds. |
| 503 | `CLAIM_CAPACITY` | Step 1 | Too many claims are waiting across the service. Wait `retry_after` seconds. |
| 429 | `RATE_LIMITED` | Step 3 | At most 30 polls a minute from one address. Keep to the 5 second interval. |
| 404 | `CLAIM_NOT_FOUND` | Step 3 | The code or the token is wrong. Check both, or start again at Step 1. |
| 5xx | any | any | Wait, then send the same request again. |

Do not send the same request again after a 4xx other than 429. Act on the table.

## More

- Human readable guide to this flow: `https://www.prereason.com/docs#agent-access`
- OpenAPI description: `https://api.prereason.com/api/openapi/spec.json`
- Agent Card: `https://www.prereason.com/.well-known/agent-card.json`
- MCP Server Card: `https://www.prereason.com/.well-known/mcp/server-card.json`
