# auth.md

You are an agent. froog supports **agentic registration** ([auth.md](https://workos.com/auth-md), v0.6): you can sign a person up for froog, cloud sandboxes for AI agents, without them pasting an API key.

Every endpoint is on the API host, `https://api.froogcorp.com`. The authoritative list is in:

```http
GET https://api.froogcorp.com/.well-known/oauth-protected-resource/v1
GET https://api.froogcorp.com/.well-known/oauth-authorization-server
```

Read `issuer`, `token_endpoint`, `revocation_endpoint`, `grant_types_supported` and the `agent_auth` block, then follow the steps in order.

froog does not accept ID-JAG identity assertions. Use `anonymous` to explore, or `service_auth` when you have the person's email. Creating sandboxes needs a completed claim, and starting the free trial needs a card in Stripe Checkout, entered by the person.

## Expect a human in the loop

Nearly every step sends a credential or an email address to a host you have no prior relationship with, and agent harnesses gate exactly that. Expect to be stopped and asked for approval more than once. That is the flow working, not failing:

- Say what you are about to send and to whom, then wait for the person.
- Resume from the step you were on. Every step is retryable on its own.
- Do not try to route around the gate.

The claim token (24 hours) and the identity assertion (24 hours) outlive a pause; check `claim_token_expires` and `assertion_expires`. Only the 6-digit `user_code` is short-lived (10 minutes): if it lapses, get a new one from Step 4a.

## Step 1 — Discover

A 401 from the froog API carries:

```http
WWW-Authenticate: Bearer resource_metadata="https://api.froogcorp.com/.well-known/oauth-protected-resource/v1"
```

`agent_auth.identity_types_supported` is `["anonymous","service_auth"]`.

## Step 2 — Pick a method

0. You have your own AgentMail inbox → sign in with AgentID instead, if this server offers it (`GET https://api.froogcorp.com/v1/auth/methods` says `"agentid": true`). See "Sign in with AgentID" below.
1. You have the person's email → `service_auth`. The claim ceremony comes before any credential.
2. Neither → `anonymous`. You get a pre-claim token at once, with the action `account.read` only (`GET /v1/account`, `GET /v1/limits`). Claim later to create sandboxes and start the trial.

Before `service_auth`, confirm with the person: you will register them with **froog**, and they will sign in to bind this agent.

## Step 3 — Register

### service_auth

```http
POST https://api.froogcorp.com/v1/agent/identity
Content-Type: application/json

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

The 200 body has `claim_token` (keep it in memory) and a `claim` block (`user_code`, `verification_uri`, `expires_in`, `interval`). No `identity_assertion` yet. Go to Step 4b.

### anonymous

```http
POST https://api.froogcorp.com/v1/agent/identity
Content-Type: application/json

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

The 200 body has `identity_assertion`, `pre_claim_scopes`, `claim_token` and `post_claim_scopes`. Exchange the assertion (Step 5) to call `GET /v1/account` and `GET /v1/limits`. To let the person take ownership and start a trial, go to Step 4.

## Step 4 — Claim ceremony

The person types a 6-digit `user_code` **you give them** into a froog page, after signing in. The code does not come back to you.

### 4a. Get the ceremony materials

`service_auth`: already in the Step 3 `claim` block.

`anonymous`:

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

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

The answer has `claim_attempt` with `user_code`, `verification_uri`, `expires_in` and `interval`. The claim is bound to that email: the person must sign in to an account with that address.

### 4b. Hand off to the person

Send the link on its own first, with a sentence around it:

> Open this link and sign in to froog (Google, GitHub or an email code). Tell me once you're in and I'll send you a code to enter.
> `https://froogcorp.com/dashboard/?claim=cla_...#claim`

Only once they say they are signed in, send the code:

> Now enter this agent code: **123456**

Send both, in that order. (Signing in by email sends its own 6-digit code; a person holding both at once can type one into the wrong field.)

### 4c. Poll for completion

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

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

While waiting: `{"error":"authorization_pending"}`. Honor `interval` (5 s). On `slow_down`, add 5 s. On `expired_token`, call Step 4a again for a new code. On success you get `access_token` (a froog API key, `froog_...`, valid 90 days) and an `identity_assertion`. Drop any pre-claim token. Polling again returns the same key.

## Step 5 — Exchange the assertion

To get the pre-claim token, or a new key later (the previous key is revoked). After the claim, this works only while your current key is live: once the person revokes it (or it expires after 90 days), the answer is `invalid_grant` and you start again at Step 3.

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

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https://api.froogcorp.com/v1/
```

Response: `access_token`, `token_type=Bearer`, `expires_in`, `scope`, and a fresh `identity_assertion`.

## Step 6 — Use the access_token

```http
GET https://api.froogcorp.com/v1/account
Authorization: Bearer <access_token>
```

A pre-claim token may only read the account and its limits. Everything else answers `403 claim_required`. When the key stops working, run Step 5 with your latest assertion; if that answers `invalid_grant`, start again at Step 3.

API reference: https://froogcorp.com/docs/api/ (OpenAPI: https://froogcorp.com/openapi.json)

## Step 7 — Start the free trial

A claimed key has the action `billing.trial`: it can read billing and choose the first plan, which starts the 7-day free trial through the same Stripe Checkout a person uses. A card is required and there is one trial per card. It can't change or cancel a plan, pay from the wallet, choose a plan for an account that had its trial, or create API keys (`403 api_key_action_forbidden`): the person does those in the dashboard. Do not open another account.

**Never quote a price from memory or a web search.** `GET /v1/limits` and `GET /v1/billing` return the live plans and what the trial includes. Send the person to https://froogcorp.com/pricing/ to estimate costs.

```http
POST https://api.froogcorp.com/v1/billing/plan
Authorization: Bearer <access_token>
Content-Type: application/json

{"plan":20}
```

Show the person the returned `url`. Poll `GET /v1/billing` until the plan is active (or trialing), then create a sandbox:

```http
POST https://api.froogcorp.com/v1/sandboxes
Authorization: Bearer <access_token>
Content-Type: application/json

{"name":"first","size":"small","ttl_secs":3600}
```

During the trial every sandbox needs an auto-stop of 2 hours or less.

## Sign in with AgentID

If this server offers AgentID and you have an AgentMail inbox, sign in to froog as that inbox. froog makes (or opens) the account of your inbox address.

1. Open `https://api.froogcorp.com/v1/auth/agentid/start` in a browser. It goes to the AgentID waiting page, which shows an `auth_token`.
2. Approve with your AgentMail key:

```http
POST https://api.agentmail.to/v0/inboxes/<your inbox id>/authorize
Authorization: Bearer <your AgentMail API key>
Content-Type: application/json

{"auth_token":"<auth_token from the page>","accept_disclosure":true}
```

3. The browser returns to the froog dashboard, signed in. Create an API key there, or run `froog login` and approve it in that browser.

The free trial still needs Step 7.

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| `issuer_not_enabled` | `/v1/agent/identity` | ID-JAG is not accepted. Use `service_auth` or `anonymous`. |
| `invalid_request` | `/v1/agent/identity`, `/v1/agent/identity/claim` | Fix the body. |
| `invalid_login_hint` | `/v1/agent/identity` | `login_hint` must be an email address. |
| `invalid_claim_token` | `/v1/agent/identity/claim` | Restart at Step 3. |
| `claimed_or_in_flight` | `/v1/agent/identity/claim` | Already claimed: poll Step 4c. |
| `claim_expired` | `/v1/agent/identity/claim` | Restart at Step 3. |
| `invalid_grant` | `/v1/oauth2/token` | The registration ended (the key was revoked or expired). Restart at Step 3. |
| `unsupported_grant_type` | `/v1/oauth2/token` | Use jwt-bearer or the agent-auth claim grant. |
| `authorization_pending` | `/v1/oauth2/token` | Wait `interval` seconds and retry. |
| `expired_token` | `/v1/oauth2/token` | Call `/v1/agent/identity/claim` again. |
| `slow_down` | `/v1/oauth2/token` | Add 5 s to `interval`. |
| `rate_limited` | any | Back off (10 registrations per network an hour: an IPv4 address or an IPv6 /64). |
| `claim_required` | froog API | Finish Step 4 before this call. |
| `subscription_required` | froog API | Finish Step 7. |

## Revocation

POST `token=<access_token>&token_type_hint=access_token` to `https://api.froogcorp.com/v1/oauth2/revoke`. Always 200. Revoking a key (here, or the person on the API keys page) ends the registration: the `identity_assertion` gets no new key, and you register again (Step 3) to be claimed again.
