# auth.md

Runtype is an AI product development platform. Agents can register for scoped API credentials and later bind them to a real user account via an OTP claim ceremony.

- **Resource server**: `api.runtype.com`
- **Authorization server**: `api.runtype.com`

## Discover

1. Any 401 response from the API includes a `WWW-Authenticate` header:

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

2. Fetch the Protected Resource Metadata (PRM) at that URL to find the authorization server.

3. Fetch `https://api.runtype.com/.well-known/oauth-authorization-server` — the response includes an `agent_auth` block:
   ```json
   {
     "agent_auth": {
       "skill": "https://runtype.com/auth.md",
       "register_uri": "https://api.runtype.com/agent/auth",
       "claim_uri": "https://api.runtype.com/agent/auth/claim",
       "revocation_uri": "https://api.runtype.com/agent/auth/revoke",
       "identity_types_supported": ["anonymous"],
       "anonymous": {
         "credential_types_supported": ["api_key"]
       }
     }
   }
   ```

## Pick a method

Only `anonymous` registration is supported. No provider assertion (ID-JAG) is required.

## Register

Send a POST to the `register_uri`:

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

{
  "type": "anonymous",
  "requested_credential_type": "api_key"
}
```

Success response (201):

```json
{
  "registration_id": "reg_...",
  "registration_type": "anonymous",
  "credential_type": "api_key",
  "credential": "rt_live_...",
  "credential_expires": "2026-05-27T12:00:00.000Z",
  "scopes": ["DISPATCH:*", "FLOWS:READ", "FLOWS:WRITE", "..."],
  "claim_url": "https://api.runtype.com/agent/auth/claim",
  "claim_token": "...",
  "claim_token_expires": "2026-05-27T12:00:00.000Z",
  "post_claim_scopes": ["*"]
}
```

**Deviation from spec**: The pre-claim credential expires after **24 hours**. Agents that do not claim within this window must re-register.

## Claim ceremony

The claim ceremony binds the anonymous credential to a real user via OTP email verification.

### Step 1 — Trigger claim email

```http
POST /agent/auth/claim HTTP/1.1
Host: api.runtype.com
Content-Type: application/json

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

The user receives an email with a 6-digit OTP (valid for 10 minutes, max 5 attempts).

### Step 2 — Submit OTP

```http
POST /agent/auth/claim/complete HTTP/1.1
Host: api.runtype.com
Content-Type: application/json

{
  "claim_token": "...",
  "otp": "123456"
}
```

Success response:

```json
{
  "registration_id": "reg_...",
  "status": "claimed",
  "credential_type": "api_key",
  "credential": "rt_live_...",
  "scopes": ["*"]
}
```

**Deviation from spec**: On successful claim, the pre-claim credential is **revoked** and a **fresh credential** is issued. The old key will return 401. This is a security measure — any copies of the pre-claim key (in agent memory, logs) do not silently gain elevated permissions.

## Use the credential

```http
GET /v1/flows HTTP/1.1
Host: api.runtype.com
Authorization: Bearer rt_live_...
```

On 401, drop the credential and restart from Step 1 (Discover).

## Pre-claim limits

Pre-claim credentials have broad platform access with cost controls:

- **Models**: Workers AI only (`@cf/google/gemma-4-26b-a4b-it`, `@cf/openai/gpt-oss-120b`)
- **Spend cap**: $0.10 total
- **Daily executions**: 5
- **Surfaces**: 1 development, 0 production
- **Blocked features**: Firecrawl, Exa, Daytona, BYOK, batch, evals, schedules

After claiming, all limits are removed and the account is on the Build plan.

## Errors

| Endpoint                          | Error code                    | HTTP | Recovery                                                           |
| --------------------------------- | ----------------------------- | ---- | ------------------------------------------------------------------ |
| `POST /agent/auth`                | `unsupported_credential_type` | 400  | Use `type: "anonymous"` and `requested_credential_type: "api_key"` |
| `POST /agent/auth`                | `rate_limited`                | 429  | Wait and retry                                                     |
| `POST /agent/auth`                | `service_disabled`            | 503  | Registration is temporarily disabled                               |
| `POST /agent/auth/claim`          | `invalid_claim_token`         | 404  | Re-register                                                        |
| `POST /agent/auth/claim`          | `email_already_registered`    | 409  | Sign in at use.runtype.com directly                                |
| `POST /agent/auth/claim`          | `claimed_or_in_flight`        | 409  | Claim already initiated                                            |
| `POST /agent/auth/claim`          | `claim_expired`               | 410  | Re-register                                                        |
| `POST /agent/auth/claim/complete` | `otp_invalid`                 | 401  | Re-enter OTP                                                       |
| `POST /agent/auth/claim/complete` | `otp_expired`                 | 410  | Re-initiate claim                                                  |
| `POST /agent/auth/claim/complete` | `previously_claimed`          | 409  | Already claimed                                                    |
| `POST /agent/auth/claim/complete` | `claim_expired`               | 410  | Re-register                                                        |
| `POST /agent/auth/claim/complete` | `credential_expired`          | 410  | Re-register                                                        |

## Revocation

No agent-facing revocation endpoint for v1. On 401, drop the credential and re-register.

The `revocation_uri` in the metadata returns 501 until the agent-verified flow ships.
