# auth.md

PageBolt supports agentic registration. An AI agent can obtain a PageBolt API
key on a user's behalf via an email one-time-code (OTP) claim — no signup form.

- Resource server (the API): `https://pagebolt.dev/api/v1`
- Authorization server (registration + claim): `https://pagebolt.dev`

## 1. Discover

- On any `401` from the API, read the `WWW-Authenticate: Bearer resource_metadata="..."` header,
  or fall back to `https://pagebolt.dev/.well-known/oauth-protected-resource`.
- Fetch the Protected Resource Metadata for `resource`, `authorization_servers`, `scopes_supported`.
- Fetch `https://pagebolt.dev/.well-known/oauth-authorization-server` and read the `agent_auth` block:
  `register_uri`, `claim_uri`, `identity_types_supported`, and the per-type `*_supported` arrays.

## 2. Pick a method

PageBolt supports two methods:

- **anonymous** — get a working, reduced-quota key *immediately* (no email needed), then optionally claim it later to a user account for full quota.
- **identity_assertion + verified_email** — an emailed OTP claim; use when you already have the user's email.

## 3. Register


### Method A — anonymous (instant key, claim later)

```http
POST https://pagebolt.dev/agent/auth
Content-Type: application/json
```
```json
{ "type": "anonymous", "requested_credential_type": "api_key" }
```
You get a working key right away (reduced quota, scope `capture:trial`) plus a `claim_token`:
```json
{ "registration_type": "anonymous", "credential": "pf_live_...", "credential_expires": null,
  "scopes": ["capture:trial"], "claim_token": "clm_...", "claim_token_expires": "...",
  "post_claim_scopes": ["capture:full"] }
```
Use the key immediately. To upgrade it to full quota, start a claim with the user's email:
```http
POST https://pagebolt.dev/agent/auth/claim
Content-Type: application/json
```
```json
{ "claim_token": "clm_...", "email": "user@example.com" }
```
PageBolt emails the user a secure link; then run the claim ceremony (§4). On success your
**existing key keeps working** and gains full quota — no new credential is issued.

### Method B — identity_assertion + verified_email (email-first)

```http
POST https://pagebolt.dev/agent/auth
Content-Type: application/json
```
```json
{ "type": "identity_assertion", "assertion_type": "verified_email",
  "assertion": "user@example.com", "requested_credential_type": "api_key" }
```
The response carries a `claim_token` (no credential yet). PageBolt emails the
user a secure link at this point.
```json
{ "registration_id": "reg_...", "registration_type": "email-verification",
  "claim_token": "clm_...", "claim_token_expires": "...", "post_claim_scopes": ["capture:full"] }
```

## 4. Claim ceremony

- 4a. The user opens the link in their email; PageBolt shows them a 6-digit code.
- 4b. Ask the user to paste the code back to you. (You never see it directly.)
- 4c. Submit it:
```http
POST https://pagebolt.dev/agent/auth/claim/complete
Content-Type: application/json
```
```json
{ "claim_token": "clm_...", "otp": "123456" }
```
```json
{ "status": "claimed", "credential_type": "api_key",
  "credential": "pf_live_...", "credential_expires": null, "scopes": ["capture:full"] }
```

## 5. Use the credential

Send the API key on every request:
```http
x-api-key: pf_live_...
```
The key does not expire. On a `401` from a previously-working key, drop it and
restart at Step 1.

## 6. Errors

| Code | Meaning | Action |
| --- | --- | --- |
| `rate_limited` | Too many attempts | Back off and retry later |
| `missing_verified_email` | No/invalid email at registration | Supply a valid email |
| `unsupported_assertion_type` | Method not supported | Use verified_email |
| `invalid_claim_token` | Unknown claim token | Restart at Step 3 |
| `claim_expired` | Registration expired | Restart at Step 3 |
| `otp_invalid` | Wrong/absent code | Re-read the code from the user |
| `otp_expired` | Code timed out | Re-open the email link for a fresh code |
| `too_many_attempts` | OTP attempt limit hit | Restart at Step 3 |
| `previously_claimed` | Already claimed | Use the issued key |

## 7. Revocation

There is no agent-facing revoke endpoint. If a key is revoked you'll see a
`401`; drop the key and restart at Step 1.
