# auth.md

You are an agent. Hopper supports **agentic registration** ([auth.md protocol](https://github.com/workos/auth.md), user claimed flow): you register, the user confirms a 6-digit code in their browser, and you receive a Hopper API key. Follow the steps in order.

- Authorization server (registration, claim, token): `https://withhopper.com`
- Resource server (the API the key unlocks): `https://api.withhopper.com/v1`, OpenAI-compatible

`urn:workos:agent-auth:…` is the protocol's grant name; no WorkOS account is involved.

## Step 1 — Discover

```http
GET https://withhopper.com/.well-known/oauth-authorization-server
```

The `agent_auth` block lists the endpoints used below and `identity_types_supported`. `https://withhopper.com/.well-known/oauth-protected-resource` describes the API.

## Step 2 — Pick a method

- `identity_types_supported` includes `anonymous` → **anonymous** (Step 2a). You get a working key right away with a small trial budget and no human step; the user claims it later to keep going.
- Otherwise → **service_auth** (Step 2b). No key exists until the user confirms.

### Step 2a — anonymous

```http
POST https://withhopper.com/agent/identity
Content-Type: application/json

{ "type": "anonymous" }
```

Response (200) carries `identity_assertion`, `claim_token` and `pre_claim_scopes`. Exchange the assertion once for a trial key:

```http
POST https://withhopper.com/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>
```

The `access_token` in the response is an `sk_hopper_` key with a small trial budget. Use it now. On `503 temporarily_unavailable`, retry the same request; retrying after success returns the same key. To keep going past the trial, ask the user for their email and start the claim:

```http
POST https://withhopper.com/agent/identity/claim
Content-Type: application/json

{ "claim_token": "clm_...", "email": "user@example.com" }
```

Then continue at Step 3 with the `claim_attempt` block. When the claim completes, Step 4 returns a new key on the user's account and the trial key stops working: replace it.

### Step 2b — service_auth

You need the user's email address. It must be an address on the Hopper account they will sign in with, or the one they will sign up with. If you don't know it, ask. `git config user.email` is a reasonable guess to confirm with them. Before sending it, tell the user you are registering with Hopper on their behalf.

```http
POST https://withhopper.com/agent/identity
Content-Type: application/json

{ "type": "service_auth", "login_hint": "user@example.com" }
```

Response (200):

```json
{
  "registration_id": "reg_...",
  "registration_type": "service_auth",
  "claim_url": "https://withhopper.com/agent/identity/claim",
  "claim_token": "clm_...",
  "claim_token_expires": "2026-09-27T19:00:00.000Z",
  "post_claim_scopes": ["api"],
  "claim": {
    "user_code": "123456",
    "expires_in": 600,
    "verification_uri": "https://withhopper.com/claim?claim_attempt_token=cla_...",
    "interval": 5
  }
}
```

`claim_token` is returned once. Keep it for Step 4: in memory, or, if your commands don't share memory, in a private temp file (mode 600, outside the project) that you delete after Step 4. Never show it to the user, commit it, or send it anywhere but `withhopper.com`.

## Step 3 — Hand off to the user

Show `verification_uri` and `user_code` in one message, and open the link in their browser if you can (`open` on macOS, `xdg-open` on Linux):

> Open this link, sign in or sign up, and enter this code: **123456**
> https://withhopper.com/claim?claim_attempt_token=cla_...

The code goes into the page, not back to you.

## Step 4 — Poll for the key

Every `interval` seconds:

```http
POST https://withhopper.com/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=clm_...
```

| Response | Meaning | Do |
| :--- | :--- | :--- |
| 400 `authorization_pending` | User hasn't entered the code yet | Keep polling |
| 429 `slow_down` | Polling too fast | Wait a minute |
| 400 `expired_token` | The 10-minute code expired | Step 5 |
| 503 `temporarily_unavailable` | Key service unavailable; retrying is safe | Keep polling |
| 400 `invalid_grant` | Registration expired (24 h), or the key was delivered more than 10 minutes ago | Register again |
| 200 | Done | Save the key |

If you lose the 200 response, poll again with the same `claim_token` within 10 minutes: you get the same key back, never a second one.

Success (200):

```json
{ "access_token": "sk_hopper_...", "token_type": "Bearer", "scope": "api" }
```

`access_token` is a regular Hopper API key. It does not expire, and it can't be fetched again after 10 minutes. Save it where the project keeps server secrets (usually `HOPPER_API_KEY` in a gitignored `.env`) without printing it. The user can revoke it at https://withhopper.com/console/keys.

## Step 5 — New code

If the code expired before the user entered it:

```http
POST https://withhopper.com/agent/identity/claim
Content-Type: application/json

{ "claim_token": "clm_..." }
```

The response carries a fresh `claim_attempt` block (same shape as `claim` above). Go back to Step 3. A `410 claim_expired` means the registration itself is gone: start again at Step 2.

## Step 6 — Call the API

```http
GET https://api.withhopper.com/v1/models
Authorization: Bearer sk_hopper_...
```

Next: https://withhopper.com/skill.md

## Errors

| Error | Endpoint | Meaning |
| :--- | :--- | :--- |
| `invalid_login_hint` | `/agent/identity`, `/agent/identity/claim` | Not an email address |
| `anonymous_not_enabled` | `/agent/identity` | Anonymous start is off; use `service_auth` |
| `identity_assertion_not_enabled` | `/agent/identity` | ID-JAG assertions are not accepted yet; use `service_auth` |
| `already_claimed` | `/agent/identity/claim` | Poll `/oauth2/token` instead |
| `slow_down` | all | Rate limited per IP |
| `agent_auth_unavailable` | all | Registration is down; the user can create a key at https://withhopper.com/console/keys |

Pricing: https://docs.withhopper.com/models · Terms: https://withhopper.com/terms · Privacy: https://withhopper.com/privacy · Contact: founders@withhopper.com
