# JSON Web Key Set

These endpoints implement the auth.md agent onboarding protocol. They use OAuth-style error bodies (`{"error": "...", "error_description": "..."}`) instead of the uniform MachineMail error envelope.

| Method | Path | Summary |
| --- | --- | --- |
| `GET` | `/.well-known/jwks.json` | [JSON Web Key Set](/content/docs/reference/api/agent-auth#json-web-key-set/index.html) |
| `GET` | `/.well-known/oauth-authorization-server` | [OAuth Authorization Server Metadata](/content/docs/reference/api/agent-auth#oauth-authorization-server-metadata/index.html) |
| `GET` | `/.well-known/oauth-protected-resource` | [OAuth Protected Resource Metadata](/content/docs/reference/api/agent-auth#oauth-protected-resource-metadata/index.html) |
| `GET` | `/.well-known/oauth-protected-resource/{path}` | [OAuth Protected Resource Metadata (Path-Suffixed)](/content/docs/reference/api/agent-auth#oauth-protected-resource-metadata-path-suffixed/index.html) |
| `POST` | `/agent/event/notify` | [Receive Security Event Token](/content/docs/reference/api/agent-auth#receive-security-event-token/index.html) |
| `POST` | `/agent/identity` | [Register Agent Identity](/content/docs/reference/api/agent-auth#register-agent-identity/index.html) |
| `POST` | `/agent/identity/claim` | [Start Claim Ceremony](/content/docs/reference/api/agent-auth#start-claim-ceremony/index.html) |
| `GET` | `/auth.md` | [Agent Onboarding Document](/content/docs/reference/api/agent-auth#agent-onboarding-document/index.html) |
| `GET` | `/oauth2/authorize` | [Authorization Endpoint](/content/docs/reference/api/agent-auth#authorization-endpoint/index.html) |
| `POST` | `/oauth2/register` | [Register OAuth Client](/content/docs/reference/api/agent-auth#register-oauth-client/index.html) |
| `POST` | `/oauth2/revoke` | [Revoke Token](/content/docs/reference/api/agent-auth#revoke-token/index.html) |
| `POST` | `/oauth2/token` | [Token Endpoint](/content/docs/reference/api/agent-auth#token-endpoint/index.html) |

## JSON Web Key Set

http

```
GET /.well-known/jwks.json
```

The service’s public signing key (RFC 7517). Verifies the identity assertions and access tokens this service issues.

**Authentication:** None.

#### Response

**200** JWKS (`application/json`)

Headers:

| Field          | Description                  |
| ---------------| ----------------------------- |
| `Cache-Control`| `string`                     |
| `keys`        | `Record<string, unknown>[]` Required

#### Errors

| Status | Meaning |
| --- | --- |
| `500` | Unexpected server error. |

#### Example

Request

```
curl "https://api.machinemail.dev/.well-known/jwks.json"
```

Response 200

```
{
  "keys": [
    {
      "key": "value"
    }
  ]
}
```

## OAuth Authorization Server Metadata

http

```
GET /.well-known/oauth-authorization-server
```

RFC 8414 plus the auth.md `agent_auth` block. `identity_types_supported` lists the registration types MachineMail accepts.

**Authentication:** None.

#### Response

**200** The metadata (`application/json`)

| Field                         | Description                                           |
| ------------------------------| -----------------------------------------------------|
| `resource`                   | Required                                            |
| `authorization_servers`      | Required                                            |
| `scopes_supported`           | Required                                            |
| `bearer_methods_supported`   | Required                                            |
| `issuer`                     | Required                                            |
| `authorization_endpoint`      | Required                                            |
| `token_endpoint`             | Required                                            |
| `registration_endpoint`      | Required                                            |
| `revocation_endpoint`        | Required                                            |
| `device_authorization_endpoint`| Required                                          |
| `jwks_uri`                  | Required                                            |
| `service_documentation`      | Required                                            |
| `grant_types_supported`      | Required                                            |
| `response_types_supported`   | Required                                            |
| `response_modes_supported`    | Required                                           |
| `code_challenge_methods_supported`| Required                                     |
| `token_endpoint_auth_methods_supported`| Required                                 |
| `revocation_endpoint_auth_methods_supported`| Required                           |
| `client_id_metadata_document_supported`| Required                                |
| `authorization_response_iss_parameter_supported`| Required                       |
| `agent_auth`                | Required                                            |

#### Errors

| Status | Meaning |
| --- | --- |
| `500` | Unexpected server error. |

#### Example

Request

```
curl "https://api.machinemail.dev/.well-known/oauth-authorization-server"
```

Response 200

```
{
  "resource": "string",
  "authorization_servers": ["string"],
  "scopes_supported": ["inboxes.read"],
  "bearer_methods_supported": ["string"],
  "issuer": "string",
  "authorization_endpoint": "string",
  "token_endpoint": "string",
  "registration_endpoint": "string",
  "revocation_endpoint": "string",
  "device_authorization_endpoint": "string",
  "jwks_uri": "https://example.com",
  "service_documentation": "string",
  "grant_types_supported": ["string"],
  "response_types_supported": ["string"],
  "response_modes_supported": ["string"],
  "code_challenge_methods_supported": ["string"],
  "token_endpoint_auth_methods_supported": ["string"],
  "revocation_endpoint_auth_methods_supported": ["string"],
  "client_id_metadata_document_supported": false,
  "authorization_response_iss_parameter_supported": false,
  "agent_auth": {
    "skill": "string",
    "identity_endpoint": "string",
    "claim_endpoint": "string",
    "events_endpoint": "string",
    "identity_types_supported": ["anonymous"],
    "identity_assertion": {
      "assertion_types_supported": ["string"]
    },
    "events_supported": ["string"]
  }
}
```

## OAuth Protected Resource Metadata

http

```
GET /.well-known/oauth-protected-resource
```

RFC 9728. Referenced by the `WWW-Authenticate` header on `401` responses from `/v0` and `/mcp`.

**Authentication:** None.

#### Response

**200** The metadata (`application/json`)

| Field                        | Description                       |
| -----------------------------| ----------------------------------|
| `resource`                  | Required                         |
| `authorization_servers`     | Required                         |
| `scopes_supported`          | Required                         |
| `bearer_methods_supported`  | Required                         |
| `resource_name`             | Required                         |
| `resource_logo_uri`        | Required                         |
| `resource_documentation`    | Required                         |

#### Errors

| Status | Meaning |
| --- | --- |
| `500` | Unexpected server error. |

#### Example

Request

```
curl "https://api.machinemail.dev/.well-known/oauth-protected-resource"
```

Response 200

```
{
  "resource": "string",
  "authorization_servers": ["string"],
  "scopes_supported": ["inboxes.read"],
  "bearer_methods_supported": ["string"],
  "resource_name": "string",
  "resource_logo_uri": "https://example.com",
  "resource_documentation": "string"
}
```

## Receive Security Event Token

http

```
POST /agent/event/notify
```

RFC 8935 push delivery from a trusted agent provider. The body is a signed SET (`typ: secevent+jwt`) with `aud` equal to the issuer, a unique `jti`, and an `events` claim containing `https://schemas.workos.com/events/agent/auth/identity/assertion/revoked`. Every registration for that `(iss, sub)` is revoked.

**Authentication:** None.

#### Request body

Content type: `application/secevent+jwt`. Required.

Type: `string`

#### Response

**202** Accepted (empty body)

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | SET rejected. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl -X POST "https://api.machinemail.dev/agent/event/notify" \
  -H "Content-Type: application/secevent+jwt" \
  --data-binary @body
```

## Register Agent Identity

http

```
POST /agent/identity
```

Dispatches on `type` (auth.md). No API key is ever returned.

`anonymous` returns a pre-claim `identity_assertion` and the sandbox `inbox` (one ephemeral inbox on the shared domain); exchange the assertion at `/oauth2/token` for an access token.

`service_auth` withholds the assertion and returns a `claim` block until the named user (`login_hint`) confirms.

`identity_assertion` verifies an ID-JAG from a trusted provider. When the verified email belongs to an existing user it answers `401 interaction_required` with a `claim_token` and `claim` block; when `auth_time` is too old, `401 login_required` with `max_age`. Both set `WWW-Authenticate: AgentAuth error="..."`.

Registration is rate limited per requester and overall: `429 rate_limited` with `Retry-After`. `anonymous` and `service_auth` share a cap per requester of at most 1 per rolling hour and 3 per rolling 24 hours, and a platform-wide cap of about 100 per rolling hour and 1000 per rolling 24 hours.

**Authentication:** None.

#### Request body

Content type: `application/json`. Required.

| Field            | Description                                       |
| -----------------| --------------------------------------------------|
| `type`          | Required                                         |
| `name`          | Agent label (first 100 characters). Untrusted; shown as agent-described in the dashboard. |
| `client_name`   | Alias of name.                                  |
| `login_hint`    | service_auth: email address of the user. Required for that type. |
| `assertion_type`| identity_assertion: required for that type.      |
| `assertion`     | identity_assertion: required for that type.      |

#### Response

**200** Registered (`application/json`)

Headers:

| Field                 | Description                 |
| ----------------------| -----------------------------|
| `Cache-Control`      | `string`                    |
| `registration_id`    | Required                    |
| `agent_id`           | Required                    |
| `registration_type`  | Required                    |
| `identity_assertion` | Service-signed JWT to exchange at /oauth2/token. |
| `assertion_expires`  | `string (date-time)`       |
| `scopes`             | The granted scopes.         |
| `claim_url`          | Where to start the claim ceremony |
| `claim_token`        | Secret for /agent/identity/claim |
| `claim_token_expires`| `string (date-time)`       |
| `claim`              | service_auth registrations: the ceremony is already started. |
| `inbox`              | anonymous registrations: the ephemeral sandbox inbox. |

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | Invalid request. |
| `401` | OAuth error interaction_required or login_required. |
| `429` | OAuth error rate_limited. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl -X POST "https://api.machinemail.dev/agent/identity" \
  -H "Content-Type: application/json" \
  -d '{"type": "anonymous", "name": "Production"}'
```

Response 200

```
{
  "registration_id": "string",
  "agent_id": "string",
  "registration_type": "anonymous",
  "identity_assertion": "string",
  "assertion_expires": "2026-10-01T12:00:00.000Z",
  "scopes": ["inboxes.read"],
  "claim_url": "https://example.com",
  "claim_token": "string",
  "claim_token_expires": "2026-10-01T12:00:00.000Z",
  "inbox": {
    "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
    "email": "agent@machinemail.dev",
    "display_name": "Support Agent"
  }
}
```

## Start Claim Ceremony

http

```
POST /agent/identity/claim
```

Called by the agent. Returns a 6-digit `user_code` and a `verification_uri` to show the human, who completes the claim in the dashboard.

**Authentication:** None.

#### Request body

Content type: `application/json`. Required.

| Field            | Description                                       |
| -----------------| --------------------------------------------------|
| `claim_token`    | Required                                          |
| `email`          | Binds the claim to this human. |
| `agent_id`       | Alternative to claim_token.                        |

#### Response

**200** Claim attempt started (`application/json`)

| Field              | Description                                     |
| -------------------| -----------------------------------------------|
| `registration_id`  | Required                                       |
| `agent_id`         | Required                                       |
| `claim_attempt_id` | Required                                       |
| `status`           | Required                                       |
| `expires_at`       | `string (date-time)`  |
| `claim_attempt`    | Required                                       |
| `user_code`        | Required                                       |
| `verification_uri` | Required                                       |
| `expires_in`       | Required                                       |
| `interval`         | Required                                       |

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | Invalid request. |
| `401` | Invalid claim_token. |
| `404` | Unknown or revoked registration. |
| `409` | Already claimed. |
| `410` | Claim expired. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl -X POST "https://api.machinemail.dev/agent/identity/claim" \
  -H "Content-Type: application/json" \
  -d '{"email": "agent@machinemail.dev"}'
```

Response 200

```
{
  "registration_id": "string",
  "agent_id": "string",
  "claim_attempt_id": "string",
  "status": "initiated",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "claim_attempt": {
    "user_code": "string",
    "verification_uri": "https://example.com",
    "expires_in": 1,
    "interval": 1
  }
}
```

## Agent Onboarding Document

http

```
GET /auth.md
```

Markdown instructions for agents (auth.md protocol).

**Authentication:** None.

#### Response

**200** auth.md (`text/markdown`)

| Field          | Description                  |
| ---------------| ----------------------------- |
| `Cache-Control`| `string`                     |

#### Errors

| Status | Meaning |
| --- | --- |
| `500` | Unexpected server error. |

#### Example

Request

```
curl "https://api.machinemail.dev/auth.md"
```

## Authorization Endpoint

http

```
GET /oauth2/authorize
```

OAuth 2.1 authorization code grant.

**Authentication:** None.

#### Query parameters

| Field            | Description                                        |
| -----------------| ---------------------------------------------------|
| `response_type`  | Must be code.                                      |
| `client_id`      | From registration.                                 |
| `redirect_uri`   | One of the client’s redirect URIs.               |
| `code_challenge` | PKCE (RFC 7636). Required.                        |
| `scope`          | Space-separated Scope values. Default: every scope the client may ask for. |
| `state`          | Returned unchanged with the response.             |
| `resource`       | Must be the MCP server’s URL.                     |

#### Response

**302** To the consent page, to redirect_uri with an error, or to the error page.

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | Invalid request. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl "https://api.machinemail.dev/oauth2/authorize"
```

## Register OAuth Client

http

```
POST /oauth2/register
```

RFC 7591 Dynamic Client Registration for apps and MCP clients.

**Authentication:** None.

#### Request body

Content type: `application/json`. Required.

| Field             | Description                                         |
| ------------------| ----------------------------------------------------|
| `redirect_uris`   | Where the authorization response may be sent.     |
| `client_name`     | Shown on the consent page.                        |
| `grant_types`      | Must include authorization_code.                   |
| `response_types`   | Must include code.                                 |
| `scope`           | Values the client may ask for. Default: every scope. |
| `application_type` | Should use native or web.                         |

#### Response

**201** Registered (`application/json`) with headers:

| Field                     | Description                      |
| --------------------------| ---------------------------------|
| `client_id`              | The new client identifier.       |
| `client_id_issued_at`    | Seconds since the epoch.       |
| `client_secret`           | Confidential clients only.      |

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | Invalid request. |
| `429` | Rate limited. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl -X POST "https://api.machinemail.dev/oauth2/register" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris": ["string"]}'
```

Response 201

```
{
  "client_id": "my-client-id",
  "client_name": "string",
  "redirect_uris": ["string"],
  "grant_types": ["string"],
  "response_types": ["string"]
}
```

## Revoke Token

http

```
POST /oauth2/revoke
```

RFC 7009. Idempotent.

**Authentication:** None.

#### Request body

Content type: `application/x-www-form-urlencoded` or `application/json`. Required.

| Field           | Description                                        |
| ----------------| ---------------------------------------------------|
| `token`         | An access token, a refresh token or an identity assertion.|
| `token_type_hint` | Type of token.                                   |
| `client_id`     | Applicable for refresh and access tokens.         |
| `client_secret`  | Required for a confidential client.                |

#### Response

**200** Revoked (empty body)

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | Invalid request. |
| `401` | Invalid client. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl -X POST "https://api.machinemail.dev/oauth2/revoke" \
  --data-urlencode "token=string"
```

## Token Endpoint

http

```
POST /oauth2/token
```

The token endpoint of the MachineMail authorization server.

**Authentication:** None.

#### Request body

Content type: `application/x-www-form-urlencoded` or `application/json`. Required.

| Field              | Description                                       |
| -------------------| --------------------------------------------------|
| `grant_type`      | Required                                        |
| `code`            | Optional for authorization_code grant type.      |
| `refresh_token`   | Required for refresh_token grant type.          |
| `claim_token`     | Optional for claim grant type.                   |
| `client_id`       | Optional for authorization_code or refresh_token.| 
| `client_secret`   | Required for confidential clients.               |
| `resource`        | Optional audience pin.                           |

#### Response

**200** Access token (`application/json`)

| Field                | Description                                      |
| ---------------------| --------------------------------------------------|
| `access_token`      | The access token returned.                      |
| `expires_in`       | The expiration time of the token.                |
| `refresh_token`    | The refresh token.                                |
| `identity_assertion`| Identity assertion if applicable.                |

#### Errors

| Status | Meaning |
| --- | --- |
| `400` | Invalid request. |
| `401` | Invalid client. |
| `500` | Unexpected server error. |

#### Example

Request

```
curl -X POST "https://api.machinemail.dev/oauth2/token" \
  --data-urlencode "grant_type=string"
```

Response 200

```
{
  "access_token": "string",
  "expires_in": 3600,
  "refresh_token": "string"
}
```
