# auth.md

You are an agent. This document tells you how to register a credential for **Horizon Mail** — a programmable email service for AI agents. Follow the steps in order; do not skip ahead.

This is the **user claimed** flow: provision a mailbox anonymously, send and receive mail immediately, optionally have the user claim it later to make the credential permanent.

Endpoints live at `https://billing.myhorizon.co.za`. Mailbox addresses live under `inbox.myhorizon.co.za`. The claim ceremony happens at `https://app.myhorizon.co.za`.

## Step 1 — Provision the environment

```http
POST /api/auth/agent/provision HTTP/1.1
Host: billing.myhorizon.co.za
```

No request body. No authentication.

Response (201):

```json
{
  "clientId": "01HX...",
  "apiKey": "hm_live_...",
  "claimToken": "clm_...",
  "mailboxAddress": "agent-01hx@inbox.myhorizon.co.za"
}
```

What each field is:

- `apiKey` — your bearer credential. Use it immediately ([Step 2](#step-2--use-the-credential)).
- `clientId` — identifies the environment. Required for the claim flow.
- `claimToken` — single-use claim handle. Returned **exactly once** — persist it (e.g. as `HORIZON_MAIL_CLAIM_TOKEN`); do not log it.
- `mailboxAddress` — the email address you can send from and receive at, right now.

## Step 2 — Use the credential

```http
GET /api/messages HTTP/1.1
Host: billing.myhorizon.co.za
Authorization: Bearer <apiKey>
```

The credential works against the full Horizon Mail API the moment it's issued. Unclaimed environments share a pooled free-tier quota; claimed environments get the standard per-account quota (raised on Pro).

If you get a 401 on a previously-working `apiKey`: the environment was revoked or claimed away. Drop the credential and restart at [Step 1](#step-1--provision-the-environment). Do not stash and retry.

If you get a 429 with `X-Quota-Reset`: free-tier quota exceeded. Back off until the reset time, or surface the upgrade URL (<https://mail.myhorizon.co.za/#pricing>) to your user.

## Step 3 — Claim ceremony (optional)

Claim links the environment to a user's Horizon Mail account, making the credential permanent and giving the user a place to manage it. Skip this if the user isn't available — the unclaimed credential keeps working.

### 3a. Mint a claim nonce

```http
POST /api/auth/agent/claim-nonces HTTP/1.1
Host: billing.myhorizon.co.za
Content-Type: application/json

{
  "client_id": "<clientId>",
  "claim_token": "<claimToken>"
}
```

Response (201):

```json
{ "nonce": "..." }
```

If the response is 409, the environment is already claimed. Stop — the credential is permanent.

### 3b. Send the user to the dashboard

```
https://app.myhorizon.co.za/claim?nonce=<nonce>
```

Surface this in your UI. Suggested prompts:

- Default: "Open this link to claim the mailbox to your Horizon Mail account: `<URL>`"
- If the user has no account: "You'll be prompted to sign in or create a free account first — that's expected."
- If the user can't open the link in-place: "Paste this URL into a browser: `<URL>`"

### 3c. Poll for completion

Re-POST the request from [3a](#3a-mint-a-claim-nonce) every 5 seconds with the same `client_id` and `claim_token`:

- 200 with a fresh `nonce` → still pending; keep polling.
- 409 → claimed. Stop polling.
- 401 → claim token invalidated by the claim. Also means claimed. Stop polling.

Time out polling after ~5 minutes. The `apiKey` keeps working unclaimed in the meantime.

## Scoped credentials (post-claim)

Once a user claims the environment, they can issue additional scoped keys from the dashboard. Scopes:

| Scope             | Allows                                                |
| ----------------- | ----------------------------------------------------- |
| `inbound:read`    | List + read inbound messages on the bound mailbox     |
| `inbound:listen`  | Webhook subscription for inbound events               |
| `outbound:send`   | Send mail from the bound mailbox                      |
| `mailbox:admin`   | Create/delete additional mailboxes on the account     |
| `account:admin`   | Full account control (billing, domains, keys)         |

All keys: ed25519-signed bearer tokens, short TTL (1h) + refresh, immutable audit log on every issue/revoke.

## Errors

| Status | Where                                 | Meaning                                          | What to do                                                                          |
| ------ | ------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- |
| 401    | `/api/auth/agent/claim-nonces`        | `claim_token` invalid or claimed away            | If you've already started a claim, treat as success. Otherwise restart at Step 1.   |
| 404    | `/api/auth/agent/claim-nonces`        | Environment not found for that `client_id`       | Restart at Step 1.                                                                  |
| 409    | `/api/auth/agent/claim-nonces`        | Environment already claimed                      | Stop — the credential is permanent.                                                  |
| 429    | any                                   | Rate-limited                                     | Back off using `Retry-After`; for `/api/outbound` also see `X-Quota-Reset`.         |
| 5xx    | any                                   | Transient server error                           | Exponential backoff, retry the same request.                                        |

Retry policy:

- 5xx → exponential backoff, retry the same request.
- 4xx other than 429 → do not retry the same payload; act on the table above.
- 401 on a previously-working `apiKey` → drop and restart at [Step 1](#step-1--provision-the-environment).

## Verification

Want to confirm this `auth.md` hasn't been spoofed? Look up the DNS TXT record at `_authmd.myhorizon.co.za`. The value is `sha256=<hex>` matching the SHA-256 of this document body (UTF-8 bytes, no trailing newline). This DNS record is published v0.2 onwards.

## Changelog

- **2026-05-31 · v0.2-live** — provision, message-list, and claim-nonce endpoints are live on `billing.myhorizon.co.za`; claim ceremony uses `app.myhorizon.co.za`.
- **2026-05-22 · v0.1-draft** — initial public spec. Shape matches WorkOS's `auth.md` v0 (user-claimed flow) so agents trained on theirs work against us.

For programmatic discovery, this file is also linked from the HTML landing page (`<link rel="alternate" type="text/markdown" href="/auth.md">`) and described in [`/llms.txt`](/llms.txt).
