Agent Auth · MachineMail Docs
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 |
GET |
/.well-known/oauth-authorization-server |
OAuth Authorization Server Metadata |
GET |
/.well-known/oauth-protected-resource |
OAuth Protected Resource Metadata |
GET |
/.well-known/oauth-protected-resource/{path} |
OAuth Protected Resource Metadata (Path-Suffixed) |
POST |
/agent/event/notify |
Receive Security Event Token |
POST |
/agent/identity |
Register Agent Identity |
POST |
/agent/identity/claim |
Start Claim Ceremony |
GET |
/auth.md |
Agent Onboarding Document |
GET |
/oauth2/authorize |
Authorization Endpoint |
POST |
/oauth2/register |
Register OAuth Client |
POST |
/oauth2/revoke |
Revoke Token |
POST |
/oauth2/token |
Token Endpoint |
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"
}