# auth.md

Primitive Agent Auth — how an agent obtains and uses credentials for the Primitive API. This page is the prose companion to the discovery metadata published at:

- `/.well-known/oauth-authorization-server` (RFC 8414)
- `/.well-known/oauth-protected-resource` (RFC 9728)
- `/.well-known/api-catalog` (RFC 9727)

Use the `resource` value from `/.well-known/oauth-protected-resource` as the API base. The production API base is `https://api.primitive.dev/v1`.

## Discover

An agent should first probe the resource origin for protected-resource metadata:

```
GET https://www.primitive.dev/.well-known/oauth-protected-resource
```

The response advertises the resource server, the authorization server origin, bearer token support, and an `agent_auth` block describing the agent registration surface. Primitive credentials are organization-scoped and do not advertise granular OAuth scopes today. The same `agent_auth` block is also reachable from any 401 response to a Primitive API call via the spec-shaped header:

```
WWW-Authenticate: Bearer realm="Primitive API", resource_metadata="https://www.primitive.dev/.well-known/oauth-protected-resource"
```

Then fetch the authorization-server metadata:

```
GET https://www.primitive.dev/.well-known/oauth-authorization-server
```

Read `issuer`, `authorization_endpoint`, `token_endpoint`, `revocation_endpoint`, `registration_endpoint`, and `agent_auth` from that response before choosing a registration method.

## Pick a Method

Two agent identity types are advertised today:

- **`emailless`** (zero-touch), a headless agent gets an account in one unauthenticated POST, with no email and no human in the loop. It returns an API key and a provisioned managed `*.primitive.email` inbox immediately. The account is on the `agent` plan: reply-only (it can only reply to addresses that have already sent it authenticated mail) with tight send limits. It can be upgraded later by confirming an email (see Upgrade an emailless account). Credential type: `api_key` (prefixed `prim_`). This is the fast path for autonomous agents.
- **`anonymous`** (email-verified), an agent completes an email verification round-trip and receives OAuth credentials for a managed `*.primitive.email` address. Credential type: `oauth_access_token`. A bonus `signup_code` may be supplied but is not required.

Identity assertion registration is not advertised until that flow is implemented end to end.

## Register (emailless, zero-touch)

The fastest path for a headless agent. One unauthenticated POST returns a usable API key plus a provisioned inbox. In production:

```
POST https://api.primitive.dev/v1/agent/accounts
Content-Type: application/json
```

Request body:

```json
{
  "terms_accepted": true,
  "device_name": "local agent"
}
```

Response (the `api_key` is shown exactly once):

```json
{
  "api_key": "prim_...",
  "org_id": "...",
  "address": "brave-crow.primitive.email",
  "plan": "agent",
  "limits": { "send_per_hour": 10, "send_per_day": 50, "...": "..." },
  "upgrade": {
    "plan": "developer",
    "description": "Confirm an email to lift the send cap and unlock sending to any recipient + Functions.",
    "claim_path": "/v1/agent/claim/start"
  }
}
```

Use the key as `Authorization: Bearer prim_...` on every `/v1` call. `GET /v1/account` reports the account's `plan`, granted `entitlements` (the `agent` plan grants `send_mail` + `send_to_known_addresses`, i.e. reply-only), and `managed_inbox_address`. The agent receives mail at its managed address and replies via `POST /v1/emails/:id/reply`; a send to an address that has not first mailed the agent is rejected with `recipient_not_allowed`. To wait for inbound mail, long-poll the forward tail: `GET /v1/emails?since=<cursor>&wait=30` returns as soon as new mail arrives.

## Register (email-verified)

Use the `agent_auth.register_uri` advertised in the AS metadata. In production it is:

```
POST https://api.primitive.dev/v1/agent/signup/start
Content-Type: application/json
```

Request body:

```json
{
  "email": "agent@example.com",
  "terms_accepted": true,
  "device_name": "local agent"
}
```

`signup_code` is optional. Include it only if you have one:

```json
{
  "email": "agent@example.com",
  "signup_code": "<your_code>",
  "terms_accepted": true,
  "device_name": "local agent"
}
```

The response returns a `signup_token`. Submit the email verification code to the matching `/agent/signup/verify` endpoint on the same API base:

```
POST <resource>/agent/signup/verify
Content-Type: application/json
```

Request body:

```json
{
  "signup_token": "<signup_token>",
  "verification_code": "123456"
}
```

The verification response returns `access_token`, `refresh_token`, `expires_in`, and organization metadata. Access tokens are prefixed `prim_oat_`.

The CLI wraps the same flow:

```
primitive agent start-agent-signup
```

It prompts for an email address (and optionally accepts a `--signup-code` flag), sends a 6-digit verification code, and stores the returned OAuth credentials locally after verification.

## Upgrade an emailless account

An `emailless` (`agent` plan) account is upgraded to a full `developer` account by confirming an email. The org id, API key, and managed inbox all carry over; the send cap lifts and sending to any recipient (plus Functions) unlocks. Authenticate with the agent's own API key:

```
POST <resource>/agent/claim/start   { "email": "you@example.com" }
POST <resource>/agent/claim/verify  { "verification_code": "123456" }
```

If the agent has no channel to relay the code, mint a browser claim link instead and hand it to a human:

```
POST <resource>/agent/claim/link    -> { "claim_url": "https://www.primitive.dev/claim?token=..." }
```

Confirming an email that already belongs to a Primitive account is rejected (`email_in_use`); the existing account is never merged.

## Claim

For agents that already have a managed subdomain and need a credential for a new client, OAuth 2.0 Dynamic Client Registration is supported (RFC 7591). Use the `registration_endpoint` from authorization-server metadata. In production it is:

```
POST https://www.primitive.dev/oauth/register
Content-Type: application/json
```

Authorization codes and access tokens follow standard PKCE flow against the endpoints listed in `/.well-known/oauth-authorization-server`. Access tokens are prefixed `prim_oat_`. Token responses include `scope: "primitive:api"`, meaning full organization-scoped Primitive v1 API access.

## Use the credential

Every authenticated API call carries the bearer in the `Authorization` header:

```
Authorization: Bearer prim_<api_key>
```

or

```
Authorization: Bearer prim_oat_<oauth_access_token>
```

API keys and OAuth access tokens are organization-scoped. The full operation surface is documented in the OpenAPI spec at `/openapi.json` (and `/openapi.yaml`).

## Errors

Unauthenticated requests return:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Primitive API", resource_metadata="https://www.primitive.dev/.well-known/oauth-protected-resource"
Content-Type: application/json

{ "success": false, "error": { "code": "unauthorized", "message": "Invalid or missing API key or OAuth access token" } }
```

Errors share a single envelope across the API:

```
{ "success": false, "error": { "code": "<machine_readable_code>", "message": "<human_readable>", "request_id": "<id>" } }
```

Common codes: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `rate_limited`, `rate_limit_exceeded`, `internal_error`.

## Revocation

API keys are deleted from dashboard settings at `/app/settings/api-keys`.

CLI OAuth sessions can be revoked with `primitive logout`. OAuth grants can also be revoked from **Settings -> Connected Apps**.

OAuth access tokens are revoked at the `revocation_endpoint` from authorization-server metadata. In production it is:

```
POST https://www.primitive.dev/oauth/revoke
```

with the token payload per RFC 7009. Revocation is immediate; cached tokens at the agent should be discarded on any 401.

## See also

- WorkOS auth.md spec: https://workos.com/auth-md
- Primitive docs: https://www.primitive.dev/docs
- Primitive llms-full bundle: https://www.primitive.dev/llms-full.txt
