# auth.md

Automo Build supports agentic registration: an agent can register its user and
obtain scoped credentials without a sign-up form. This file is the walkthrough;
the machine truth is the discovery metadata below. Spec:
https://github.com/workos/auth.md

Hosts: the API and authorization server live on `https://mcp.automo.build`.
The web app (where a person signs in) is `https://app.automo.build`.

## 1. Discover

On a `401` from `https://mcp.automo.build/mcp`, read the `WWW-Authenticate`
header's `resource_metadata` URL, or go straight to:

- Protected resource metadata (RFC 9728):
  `https://mcp.automo.build/.well-known/oauth-protected-resource/mcp`
- Authorization server metadata (RFC 8414):
  `https://mcp.automo.build/.well-known/oauth-authorization-server`

The AS metadata carries the `agent_auth` block: `skill` (this file),
`identity_endpoint`, `claim_endpoint`, `events_endpoint`,
`identity_types_supported` (`anonymous`, `identity_assertion`, `service_auth`)
and `identity_assertion.assertion_types_supported`
(`urn:ietf:params:oauth:token-type:id-jag`). The metadata wins any conflict
with this prose.

## 2. Pick a method

- You can mint an ID-JAG for your signed-in user: use `identity_assertion`.
- You only know the user's email: use `service_auth`.
- Neither: use `anonymous`. Note the deviation below: an anonymous
  registration carries no credentials and no pre-claim scopes. It exists so
  you can start the claim ceremony; everything spendable arrives after the
  user claims it.

## 3. Register

`POST https://mcp.automo.build/agent/identity` with JSON.

Identity assertion (the provider must be on our allowlist, or the response is
`issuer_not_enabled`):

```http
POST /agent/identity HTTP/1.1
Content-Type: application/json

{"type": "identity_assertion", "assertion_type": "urn:ietf:params:oauth:token-type:id-jag", "assertion": "<ID-JAG JWT>"}
```

A `200` returns `registration_id`, `identity_assertion` (our signed JWT),
`assertion_expires` and `scopes`. The same provider account always maps to the
same Automo account. A `401` with `error="interaction_required"` means the
user must confirm in the browser: run the claim ceremony with the returned
`claim` block.

Service auth:

```http
POST /agent/identity HTTP/1.1
Content-Type: application/json

{"type": "service_auth", "login_hint": "user@example.com"}
```

Returns `claim_url`, `claim_token`, `claim_token_expires`,
`post_claim_scopes` and a `claim` block
(`user_code`, `expires_in`, `verification_uri`, `interval`).

Anonymous: `{"type": "anonymous"}` returns the claim fields and
`pre_claim_scopes: []`.

Retries: send an `Idempotency-Key` header (16 to 255 printable characters; a
fresh UUID is ideal). A retry with the same key and body answers about the
same registration instead of opening a second one, with a fresh claim code if
it is still pending. The same key with a different body is refused with `422`.

## 4. Claim

Show the `user_code` to your user and send them to the `verification_uri`
(a page on `app.automo.build` where they sign in and type the code). Poll the
token endpoint at `interval` seconds:

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

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=clm_...
```

`authorization_pending` means keep polling; `slow_down` means add at least 5
seconds; `expired_token` means call
`POST /agent/identity/claim` with your `claim_token` for a fresh code. On
success you receive `access_token`, `refresh_token`, `scope`,
`identity_assertion` and `assertion_expires`.

## 5. Exchange

An `identity_assertion` exchanges for an access token at any time while it is
valid:

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

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>
```

## 5a. Credits and plans

A newly registered account starts with a one-time trial of credits on its first
workspace. When the trial runs out, you can keep going at USD 0: the
`automo_choose_free_plan` MCP tool puts the workspace on the Free plan (a
monthly credit allowance, the built-in backend, web publishing; see
https://automo.build/pricing.md). Tell your user first: choosing Free
permanently closes the one-time welcome offer on paid plans, and published
sites carry a small "Built with Automo" badge. Paid upgrades are a human step
at https://automo.build/pricing.

## 5b. Client IDs (there is nothing to obtain)

For the standard OAuth authorization-code flow there is no signup, dashboard or
registration step: Automo Build uses Client ID Metadata Documents. Your
`client_id` IS an HTTPS URL you host, pointing at a JSON document that names
your client and its `redirect_uris`; the authorization server fetches and
verifies it on the fly. Loopback redirect URIs (`127.0.0.1`, `[::1]`,
`localhost`) match with the port ignored. Dynamic client registration is
deliberately not offered. The agent flows above need no client at all.

## 6. Use the access token

Send it as a Bearer token to `https://mcp.automo.build/mcp` (Model Context
Protocol, Streamable HTTP) and list tools with `tools/list`. Tokens live one
hour; use the `refresh_token` on `POST /oauth/token`
(`grant_type=refresh_token`), or re-exchange the assertion. Scopes are listed
in the protected-resource metadata and described at
`https://mcp.automo.build/openapi.json`.

## 7. Errors

| Error | Where | Do |
| --- | --- | --- |
| `issuer_not_enabled` | /agent/identity | Your provider is not on the allowlist; use `service_auth` |
| `invalid_request` | /agent/identity | Fix the body, or mint a fresh ID-JAG |
| `interaction_required` (401) | /agent/identity | Run the claim ceremony |
| `invalid_claim_token`, `claim_expired` | /agent/identity/claim | Restart at registration |
| `authorization_pending`, `slow_down`, `expired_token` | /oauth/token | Keep polling, slow down, or refresh the code |
| `invalid_grant` | /oauth/token | The assertion or registration is gone; restart at registration |
| `Idempotency-Key` refused (422, 409) | /agent/identity | Different body, or the registration is already claimed or expired; register with a new key |
| `rate_limited` (429) | any | Wait `Retry-After` seconds; `RateLimit-Remaining` on every response shows what is left |

## 8. Revocation

Revoke a refresh token at `POST /oauth/revoke` (RFC 7009). The person can
disconnect the agent any time under Integrations in the app. Agent identity
providers deliver `identity-assertion-revoked` security events to the
`events_endpoint`; a revoked registration stops exchanging and its sessions
end at the next refresh.
