# auth.md: how agents authenticate to Willow

You are an agent. Willow has four surfaces, and each takes a different credential. The steps below cover the one that needs a ceremony, the MCP gateway, which uses standard OAuth 2.1 with dynamic client registration. Follow them in order.

| Surface | Endpoint | Credential |
|---|---|---|
| Docs MCP server | `https://docs.withwillow.ai/mcp` | None. Public and read-only. Skip this document. |
| MCP gateway | `https://<org>.withwillow.ai/mcp` (or the organization's custom gateway domain) | OAuth access token for a Willow user (Steps 1-6), or an API token with `run:impersonate` |
| Admin API and SCIM API | `https://app.withwillow.ai` | Bearer API token (`wxt_...`), see [API tokens](#api-tokens) |
| User API | `https://<org>.withwillow.ai` | Bearer API token (`wxt_...`) with a `connect:*` scope |

`<org>` is the customer's organization subdomain. Ask the user for their gateway URL if you do not have it.

`https://docs.withwillow.ai/mcp` is Willow's only public MCP server. Any subdomain of `withwillow.ai` resolves, so do not guess a gateway host, and do not treat one as Willow's own server: a `401` there is a customer gateway asking for OAuth.

## Step 1 — Discover

Call the gateway without a token. It answers `401` with a `WWW-Authenticate` header that names the Protected Resource Metadata ([RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)):

```http
POST /mcp HTTP/1.1
Host: <org>.withwillow.ai

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://<org>.withwillow.ai/.well-known/oauth-protected-resource/mcp"
```

Fetch that URL. Its `authorization_servers` array names the authorization server. Then fetch `<authorization_server>/.well-known/oauth-authorization-server` ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)). It lists `authorization_endpoint`, `token_endpoint`, `registration_endpoint`, `revocation_endpoint`, `grant_types_supported` (`authorization_code`, `refresh_token`, `client_credentials`), and `code_challenge_methods_supported` (`S256`).

Willow's metadata has no `agent_auth` block, so the WorkOS agent-auth endpoints (`identity_endpoint`, `claim_endpoint`) do not exist here.

## Step 2 — Pick a method

1. **You act for a person in the organization** (an interactive AI client): use the OAuth authorization code flow, Steps 3-6.
2. **You are a server-side agent that already knows which user it acts for**: use an API token with the `run:impersonate` scope and name the user in an `x-user-email` or `x-user-id` header on each gateway call. A human admin must issue that token. Skip to [Step 6](#step-6--use-the-access_token).
3. **You hold an IdP-issued JWT for the user**: exchange it with [Auth Exchange](https://docs.withwillow.ai/docs/admin/sso-and-auth/auth-exchange.md) (API token scope `connect:exchange-auth`).
4. **You run as a machine user with no person behind it**: send the machine user's Access Key and Secret joined with a colon as the bearer token, `Authorization: Bearer <ACCESS_KEY>:<SECRET>`. An admin creates the machine user. See [Machine Users](https://docs.withwillow.ai/docs/admin/machine-users.md).

Willow does not support `anonymous` registration, `service_auth` (email-only) registration, or `identity_assertion` with an `id-jag` assertion. Every credential traces back to a real user or a machine user an admin created.

## Step 3 — Register

Register a client with dynamic client registration ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)) at the `registration_endpoint`:

```http
POST /register
Content-Type: application/json

{ "client_name": "My Agent", "redirect_uris": ["http://localhost:3000/callback"] }
```

The response carries `client_id` and `client_secret`. Store them; reuse the same client for later sessions.

## Step 4 — Claim

The claim is the user's consent. Send the user to the `authorization_endpoint` with `response_type=code`, your `client_id` and `redirect_uri`, a `state`, and a PKCE `code_challenge` with `code_challenge_method=S256`. The user signs in through their organization's SSO and approves. Willow redirects back to your `redirect_uri` with `code` and `state`. Check that `state` matches.

There is no polling ceremony: you receive the result on the redirect.

## Step 5 — Exchange

Exchange the code at the `token_endpoint`:

```http
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<code>&redirect_uri=<redirect_uri>&client_id=<client_id>&client_secret=<client_secret>&code_verifier=<verifier>
```

The response carries `access_token`, `token_type: Bearer`, `expires_in` (`86400`, 24 hours), and `refresh_token`. When the access token expires, call the same endpoint with `grant_type=refresh_token`.

## Step 6 — Use the access_token

Send it on every MCP request to the gateway:

```http
POST /mcp
Authorization: Bearer <access_token>
```

The token is scoped to that user: you see only the MCP servers, toolkits, and tools the organization grants them, and every call is written to the audit log under their identity.

Full reference: [OAuth Authorization Server](https://docs.withwillow.ai/docs/advanced-capabilities/oauth-authorization-server.md) and [How Agents Connect to Willow](https://docs.withwillow.ai/docs/general/agent-connection-methods.md).

## Errors

| Response | Where | What to do |
|---|---|---|
| `401` with `WWW-Authenticate` | gateway | No token, or it expired. Refresh (Step 5); if refresh fails, restart at Step 1. |
| `invalid_grant` | `token_endpoint` | Code or refresh token expired, used, or revoked. Restart at Step 4. |
| `invalid_client` | `token_endpoint` | Client ID or secret wrong. Register again (Step 3). |
| `403` | gateway or API | The user or token lacks access. Ask the user or their admin; do not retry. |
| `429` | any | Rate limited. Back off and retry. |

Retry `5xx` with exponential backoff. Do not retry a `4xx` with the same request.

## Revocation

POST `token=<access_token>` (form-encoded) to the `revocation_endpoint` ([RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009)) when you are done. Admins can also cut access from Willow at any time: removing the user (manually or through SCIM deprovisioning), revoking an API token, or disabling the MCP server. You find out on the next call, which returns `401` or `403`.

## API tokens

An organization admin creates tokens under **Admin > API Tokens**. They start with `wxt_`, are shown once, and expire after 90 days by default. Agents cannot create their own.

A person can get one without a sales call: sign up on the Free plan at https://app.withwillow.ai/signup with a work email, which makes them the owner of a new organization, then generate a token. Steps and a first request: [Try the Willow API for free](https://docs.withwillow.ai/docs/developers/try-the-api.md). There is no separate sandbox or test mode; use a free organization for testing.

```http
Authorization: Bearer wxt_...
```

| Scope | Grants |
|---|---|
| `admin:read` / `admin:write` | Admin API (MCP servers, toolkits, skills, guards, users, groups, commands) |
| `admin:scim` | SCIM provisioning only |
| `connect:read` / `connect:write` | User API (a user's toolkits, skills, commands, connections) |
| `connect:exchange-auth` | Exchange external JWTs for Connect credentials |
| `run:impersonate` | Gateway tool calls as any user in the organization |
| `all` | Everything (flagged as a warning) |

Full reference: [API Tokens](https://docs.withwillow.ai/docs/admin/api-tokens.md). Machine-readable API specs: [API catalog](https://docs.withwillow.ai/.well-known/api-catalog).
