<!-- llms.txt: https://workos.com/llms.txt -->

# Agent Registration

> Agent Registration must be enabled for your environment. Contact your WorkOS account team if it is not yet available.

## Introduction

Agent Registration enables AI agents and LLM-based clients to obtain identity credentials from your AuthKit-powered service. Unlike traditional OAuth flows designed for users in a browser, Agent Registration is purpose-built for programmatic clients that need machine-readable instructions, minimal interaction, and secure credential management.

Agents discover the registration surface through your service's [`.well-known/oauth-authorization-server`](#discovery) metadata, register with one of several identity types, optionally bind to a user via a claim ceremony, and exchange the resulting identity assertion for an access token or API key.

## How it works

### Identity types

Agent Registration supports three identity types, selected by the agent based on its context:

| Type | When to use | Claim ceremony |
|------|-------------|----------------|
| `anonymous` | The agent has no user identity and wants to trial the service | Optional |
| `service_auth` | The agent knows the user's email and needs trusted access | Required |
| `refresh` | The agent's current assertion is expiring and needs rotation | Not applicable |

### Choosing identity types

Your choice of identity type depends on your application's data model:

- **If entities are owned by users** (e.g. a user's documents, personal settings), enable only `service_auth`. This ensures every agent is bound to a specific user through the claim ceremony before it can act on their behalf.
- **If the resource owner is an organization** (e.g. shared team resources, org-wide data), `anonymous` registrations work well — agents can begin interacting with organization-scoped resources immediately and optionally bind to a user later.

When enabling `anonymous`, assign it a restricted set of untrusted permissions suitable for exploration only. Sensitive operations should require trusted (post-claim) permissions so that anonymous agents cannot perform them. See [Trust levels and scopes](#trust-levels-and-scopes) for details.

### Registration flow

```
Agent                                          AuthKit                       User
  │                                               │                           │
  │─── POST /agent/identity ─────────────────────►│                           │
  │    (type: service_auth)                       │                           │
  │◄── claim.token + attempt ─────────────────────│                           │
  │    (includes verification_uri)                │                           │
  │                                               │                           │
  │─── "Open this link and read back the code" ──────────────────────────────►│
  │                                               │◄── signs in, views code ──│
  │◄── user reads code back ──────────────────────────────────────────────────│
  │                                               │                           │
  │─── POST /agent/identity/claim/complete ──────►│                           │
  │    (user_code)                                │                           │
  │◄── identity.assertion ────────────────────────│                           │
  │                                               │                           │
  │─── POST /oauth2/token ───────────────────────►│                           │
  │    (assertion exchange)                       │                           │
  │◄── access_token / api_key ────────────────────│                           │
  │                                               │                           │
```

> **Note:** The registration response includes the initial `verification_uri`. The separate `POST /agent/identity/claim` endpoint is only needed if the initial attempt expires and the agent needs to start a new one.

## Discovery

Agents discover your registration endpoints through a standards-based chain defined by [RFC 9728](https://workos.com/blog/introducing-rfc-9728-say-hello-to-standardized-oauth-2-0-resource-metadata):

1. Your API returns a `WWW-Authenticate` header with a `resource_metadata` parameter pointing to your Protected Resource Metadata document.
2. The agent fetches `/.well-known/oauth-protected-resource` and reads the `authorization_servers` array.
3. The agent fetches the Authorization Server metadata, which includes an `agent_auth` block with registration endpoints.

```bash
# Step 1 — Your API challenges with resource_metadata
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"

# Step 2 — Protected Resource Document points to the AS
curl https://example.com/.well-known/oauth-protected-resource
{
  "resource": "https://example.com",
  "authorization_servers": ["https://authkit_domain"]
}

# Step 3 — AS metadata includes agent_auth
curl https://authkit_domain/.well-known/oauth-authorization-server | jq .agent_auth
{
  "skill": "https://authkit_domain/agent/auth.md",
  "identity_endpoint": "https://authkit_domain/agent/identity",
  "claim_endpoint": "https://authkit_domain/agent/identity/claim",
  "identity_types_supported": ["anonymous", "service_auth"]
}
```

Set up the link between your protected resource and your authorization server in the WorkOS Dashboard under **Authentication → Agents → Configuration → OAuth well-known discovery**.

The `skill` field in the AS metadata points to a generated `auth.md` document — a step-by-step guide with curl commands tailored to your environment's configuration. Agents can follow it directly without needing to parse this documentation.

### Hosting auth.md

For agents to discover your service, host the `auth.md` file at the root of your domain (e.g. `https://example.com/auth.md`). The recommended approach is to reverse-proxy the WorkOS-hosted version so it always reflects your current configuration.

Find the source URL in the WorkOS Dashboard under **Authentication → Agents → Configuration → Host auth.md**, then configure your server:

```js title="next.config.js"
async rewrites() {
  return [{
    source: '/auth.md',
    destination: 'https://authkit_domain/agent/auth.md',
  }];
}
```

```json title="vercel.json"
{
  "rewrites": [
    { "source": "/auth.md", "destination": "https://authkit_domain/agent/auth.md" }
  ]
}
```

```nginx title="nginx.conf"
location = /auth.md {
    proxy_pass https://authkit_domain/agent/auth.md;
    proxy_set_header Host authkit_domain;
    proxy_ssl_server_name on;
}
```

```js title="Cloudflare Worker"
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === "/auth.md") {
      return fetch("https://authkit_domain/agent/auth.md");
    }
    return fetch(request);
  }
}
```

For Cloudflare Enterprise, skip the worker and use a [URL rewrite rule](https://developers.cloudflare.com/rules/origin-rules/tutorials/change-uri-path-and-host-header/) instead.

Replace `authkit_domain` with your environment's AuthKit domain. The Dashboard provides the exact source URL and configuration snippets for your environment.

> **Tip:** The hosted `auth.md` URL is public — agents can access it directly without a reverse proxy. You can test the full registration flow without setting up hosting first.

## Registering

### Anonymous registration

Anonymous registration creates an identity with no user binding. The agent receives an identity assertion immediately and can optionally bind to a user later via the claim ceremony.

```bash
curl -X POST https://authkit_domain/agent/identity \
  -H "Content-Type: application/json" \
  -d '{"type": "anonymous"}'
```

The response includes:

- `identity.assertion` — a signed JWT the agent uses for credential exchange
- `claim.token` — used to start a claim ceremony later (optional)
- `scopes.pre_claim` — permissions available before claiming (untrusted)
- `scopes.post_claim` — permissions available after claiming (trusted)

### Service auth registration

When the agent knows the user's email, `service_auth` registration initiates a mandatory claim ceremony:

```bash
curl -X POST https://authkit_domain/agent/identity \
  -H "Content-Type: application/json" \
  -d '{"type": "service_auth", "login_hint": "user@example.com"}'
```

The response includes a `claim.token` and `claim.attempt.verification_uri` — the agent presents this URI to the user to start the claim ceremony.

### Refresh

When an assertion is nearing expiry, the agent rotates it using the refresh token from a previous registration or claim:

```bash
curl -X POST https://authkit_domain/agent/identity \
  -H "Content-Type: application/json" \
  -d '{"type": "refresh", "refresh_token": "<refresh_token>"}'
```

## The claim ceremony

The claim ceremony binds an agent registration to a user, similar to the [OAuth 2.0 Device Authorization Flow](https://datatracker.ietf.org/doc/html/rfc8628). It's required for `service_auth` and optional for `anonymous` registrations.

### (1) Start an attempt

The agent mints a claim attempt using the claim token from registration:

```bash
curl -X POST https://authkit_domain/agent/identity/claim \
  -H "Content-Type: application/json" \
  -d '{
    "type": "service_auth",
    "claim_token": "<claim_token>",
    "login_hint": "user@example.com"
  }'
```

The response includes `attempt.verification_uri` — a URL for the user.

### (2) User confirms

The agent presents the `verification_uri` to the user. When they open it:

1. They sign in to AuthKit (if not already authenticated)
2. The page reveals a short `user_code`
3. The user reads this code back to the agent

### (3) Complete the claim

The agent submits the code along with its claim token:

```bash
curl -X POST https://authkit_domain/agent/identity/claim/complete \
  -H "Content-Type: application/json" \
  -d '{
    "claim_token": "<claim_token>",
    "user_code": "<code from user>"
  }'
```

On success, the response contains `identity.assertion` and `identity.refresh_token` — the agent's trusted credentials.

## Standalone claim ceremony

By default, the claim ceremony sends the user to AuthKit's hosted verification page to sign in and view their `user_code`. With the standalone claim ceremony, you keep the user in your own UI: your server links the user to the claim attempt and retrieves the `user_code`, which you display yourself — no redirect to AuthKit required. You still rely on WorkOS for the underlying claim; only the verification screen moves into your app.

Use the [link claim attempt](https://workos.com/docs/reference/agents/registration/link-claim-attempt) admin API endpoint when you want to:

- Render the claim ceremony in your own UI instead of AuthKit's hosted verification page.
- Link a user to an agent registration server-side, without a browser redirect.
- Control how and where the `user_code` is presented to the user.

> Configure the standalone claim ceremony from the [WorkOS Dashboard](https://dashboard.workos.com/environment/authentication/agents?tab=configuration) under **Authentication → Agents → Configuration → Claim flow page**. Set a custom page URI for the agent claim flow; when configured, users are directed to your URI instead of the default AuthKit claim page.

### How it works

In the standard claim ceremony, the user opens a `verification_uri` that points to a hosted AuthKit page, signs in, and sees the `user_code`. With the standalone approach, your server handles that step instead:

1. **Agent starts a claim attempt** — same as the [standard flow](#1-start-an-attempt). The agent receives a `claim_token` and an `attempt.verification_uri`.
2. **Your server links the user** — instead of the user opening the verification URI, your backend calls the admin API with the `claim_attempt_token` and the user's identity. The API returns the `user_code`.
3. **Your app displays the code** — show the `user_code` to the user in your own UI so they can relay it to the agent.
4. **Agent completes the claim** — same as the [standard flow](#3-complete-the-claim). The agent submits the `user_code` to finalize the binding.

> **You must authenticate the user in your own application before displaying the `user_code`.** AuthKit's hosted verification page signs the user in first; when you move that step into your own UI, you take on that responsibility. Only reveal the code to a user your application has already signed in — otherwise anyone who can reach your UI could claim the agent. WorkOS still enforces that the email you pass matches the email bound to the claim attempt, but it cannot verify that the person viewing your UI is that user.

### Linking a claim attempt

Call `PATCH /agents/claims/attempts` with your WorkOS API key, the `claim_attempt_token` from the agent's registration, and the user's identity:

```bash
curl -X PATCH https://api.workos.com/agents/claims/attempts \
  -H "Authorization: Bearer sk_example_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "link_external_user",
    "claim_attempt_token": "<claim_attempt_token>",
    "user": {
      "email": "alice@example.com",
      "external_id": "user_abc123"
    }
  }'
```

The `user.external_id` identifies the user in your system. If no WorkOS user exists with that external ID, one is created automatically. If a user already exists and the email does not match, the request is rejected.

The response includes the `user_code` the agent needs to complete the claim:

```json
{
  "id": "agent_reg_01EHWNCE74X7JSDV0X3SZ3KJNY",
  "status": "unverified",
  "user_code": "BCDF-GHJK",
  "organizations": [
    {
      "id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY",
      "name": "Acme Corp"
    }
  ]
}
```

### Organization selection

When the user belongs to multiple organizations, include `organization_id` to specify which organization the agent should act within:

```bash
curl -X PATCH https://api.workos.com/agents/claims/attempts \
  -H "Authorization: Bearer sk_example_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "link_external_user",
    "claim_attempt_token": "<claim_attempt_token>",
    "user": {
      "email": "alice@example.com",
      "external_id": "user_abc123"
    },
    "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY"
  }'
```

If the user belongs to multiple organizations and no `organization_id` is provided, the API returns a `409` error with code `organization_selection_required` and the list of available organizations in the response. Use one of the returned organization IDs to retry the request.

## Credential exchange

After obtaining an identity assertion (from registration or claim completion), the agent exchanges it at the token endpoint for an access token or API key:

```bash
curl -X POST https://authkit_domain/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  --data-urlencode "assertion=<identity_assertion>"
```

The credential type (access token or API key) and its lifetime are configured per environment in the WorkOS Dashboard.

### Access token claims

Agent access tokens include the following claims:

| Claim | Description |
|-------|-------------|
| `iss` | The issuer URL for the environment |
| `aud` | The audience — the environment's `client_id`, or the `resource` URI if one was requested |
| `sub` | The agent registration ID (e.g. `agent_reg_01ABC...`) |
| `org_id` | The organization the agent is acting within |
| `scope` | Space-separated list of granted permissions |
| `act` | Delegation claim identifying the user who authorized the agent (e.g. `{"sub": "user_01DEF..."}`) |
| `jti` | Unique token identifier |
| `iat` | Issued-at timestamp |
| `exp` | Expiration timestamp |

The `act` claim follows the [RFC 8693 delegation pattern](https://datatracker.ietf.org/doc/html/rfc8693#section-4.1) and is present only after a successful claim ceremony binds a user to the registration.

## Validating credentials

Once an agent has exchanged its assertion for a credential, your application needs to validate that credential on incoming requests.

**Access tokens** — For short-lived access tokens, you can validate the JWT locally by verifying the signature and checking expiration. This avoids a network round-trip on every request. If you need to check whether a token has been revoked before expiry, call the [validate credential endpoint](https://workos.com/docs/reference/agents/registration/validate-credential).

**API keys** — API keys must be validated server-side. Use the [validate credential endpoint](https://workos.com/docs/reference/agents/registration/validate-credential) to confirm the key is still active and retrieve its associated registration.

### Inspecting a registration

To see full details about the agent behind a credential — its identity, organization, claim status, and timestamps — use the [get registration endpoint](https://workos.com/docs/reference/agents/registration/get-registration) with the registration ID from the access token's `sub` claim.

## Trust levels and scopes

Agent Registration uses two trust levels to control API access:

- **Untrusted** (pre-claim) — the default for anonymous registrations before a claim ceremony. Grants limited scopes suitable for exploration.
- **Trusted** (post-claim) — granted after a successful claim ceremony. Grants full scopes.

Configure which scopes map to each trust level in the WorkOS Dashboard under your environment's agent auth settings.

## Dashboard setup

Configure Agent Registration in the [WorkOS Dashboard](https://dashboard.workos.com/environment/authentication/agents) under **Authentication → Agents**:

### Registrations

The Registrations tab lists all agent registrations created against your environment, including their current status (`unverified`, `verified`, `expired`, `revoked`), the associated user, and the organization.

### Methods

Enable the identity types agents can use to register:

- **Service auth** — agents register on behalf of a user, confirming identity through a claim ceremony
- **Anonymous** — agents register instantly with no credentials; users claim ownership when ready

Each method can be independently enabled or disabled.

### Configuration

#### Credential settings

Set the credential type and lifetime for all agent registrations:

- **Credential type** — choose whether the token exchange mints an **access token** (short-lived, re-mintable) or an **API key** (durable, persisted by the agent)
- **Credential expiration** — the lifetime of issued credentials

#### Trust level permissions

Define which permissions are available at each trust level:

- **Trusted permissions** — granted after a successful claim ceremony. These are the full set of permissions for verified agents.
- **Untrusted permissions** — granted to anonymous registrations before a claim. Use these for limited, exploratory access.

Only permissions that are [enabled for organization API keys](https://workos.com/docs/authkit/api-keys/configuring-api-keys/configuring-available-permissions) appear here. To add a permission to the list, enable it under *Authorization > Configuration > Organization API key permissions* in the Dashboard.

#### Discovery

The Discovery section shows:

- **Host auth.md** — the source URL for your environment's generated `auth.md` skill document, with a configuration dialog showing reverse-proxy setup for Nginx, Next.js, Vercel, and Cloudflare (see [Hosting auth.md](#hosting-auth-md) above)
- **OAuth well-known discovery** — the authorization server metadata URL that agents use to locate your registration endpoints

## Testing

Once your environment is configured, test the full flow by prompting any AI agent:

```
Can you follow this guide to set me up with this service? https://example.com/auth.md
```

The agent will follow the instructions in `auth.md` to register and exchange its identity assertion for credentials it can use to call your API. The agent will also guide the user through the claim flow if necessary.
