# auth.md: signing in to Doing Work as an agent

Doing Work is software for trade shops. An agent works for one person, a shop owner or worker, and can only do what that person can do.

There are three ways in. All end with a bearer token you send as `Authorization: Bearer <token>`.

## 1. OAuth with PKCE (for MCP clients)

1. Call the MCP endpoint, https://doingwork.work/mcp, without a token. You get 401 with a `WWW-Authenticate` header that points at https://doingwork.work/.well-known/oauth-protected-resource/mcp.
2. Read https://doingwork.work/.well-known/oauth-authorization-server for the endpoints.
3. Register your client at https://doingwork.work/v1/oauth/register (RFC 7591). Public clients only: no secret. Redirects must be https, or http on loopback.
4. Send the person to https://pages.doingwork.work/p/oauth/authorize with a PKCE S256 challenge and `resource=https://doingwork.work/mcp`. They sign in with an emailed link and approve your client.
5. Trade the code and verifier at https://doingwork.work/v1/oauth/token. You get a short lived access token and a refresh token. Refresh tokens work once; reusing one ends the whole chain.

A token bound to the MCP endpoint works only there, not on the REST API.

## 2. A named agent key (for REST, MCP and A2A)

The shop owner makes a key for your agent with their own sign in (POST https://doingwork.work/v1/keys, owner only, not callable by an agent). The key starts with `dwk_`, is shown once, and works in that one shop only.

A key can be narrowed with scopes: `read_only`, `no_money`. `read_only` makes no changes. `no_money` cannot charge, refund or change payments. A key never does more than the owner can. The owner can revoke it at any time, and it stops at once.

## 3. Register yourself and ask the owner to claim you (agent_auth)

Use this when you have no key yet. Field names follow the agent_auth profile in https://doingwork.work/.well-known/oauth-authorization-server.

1. POST https://doingwork.work/v1/agent/identity with `{"type":"anonymous"}`. Optional: `client_name` (shown to the owner) and `scope` (`read_only`, `no_money`, space separated) to ask for a narrower key. You get a `claim_token` that lasts 24 hours. Keep it secret. Until you are claimed you can only reach public calls.
2. Ask the shop owner for their email, then POST https://doingwork.work/v1/agent/identity/claim with `{"claim_token":"...","email":"..."}`. Shortcut: register with `{"type":"service_auth","login_hint":"<email>"}` to get the same `claim` block in one call.
3. Tell the person to open `verification_uri` (a page on https://pages.doingwork.work/p/agents/claim), sign in with that email, and type the 6 digit `user_code` you show them. The code lasts 10 minutes. Never put the code in a link. Only a shop owner can finish a claim, and they pick which shop you work in.
4. Poll https://doingwork.work/v1/oauth/token with `grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=...` every `interval` seconds. `authorization_pending` means keep waiting, `slow_down` means add 5 seconds, `expired_token` means start a new claim, `access_denied` means the person said no.
5. On success `access_token` is a named agent key (`dwk_`) for that shop with the scopes in `scope`. It is handed over once and does not expire; the owner can revoke it.

Not offered: `identity_assertion` (no issuers are trusted, you get `issuer_not_enabled`) and an events endpoint.

## What you can call

- REST: https://doingwork.work/v1, described by https://doingwork.work/v1/openapi.json. Send `Doing-Work-Version: 2026-11-01`. Writes accept an `Idempotency-Key` header.
- MCP: https://doingwork.work/mcp (streamable HTTP). `tools/list` returns every tool you may call.
- A2A: https://doingwork.work/a2a (JSON-RPC, A2A 1.0), described by https://doingwork.work/.well-known/agent-card.json. Send `SendMessage` with one data part, `{"op": "<op id>", "input": {...}}`, using a named agent key. There is no model behind it: the op runs exactly as on REST and MCP, with the same scopes, permissions and owner approvals. A held write comes back as an input required task; call `GetTask` later for the decision. Each `messageId` runs once.

Some actions only run on a hosted page a person opens, such as signing a quote or paying an invoice. Those are marked `x-human-only` in the OpenAPI file and are never MCP tools.

## Errors

Every error is JSON with `code`, `message` and `status`. A 429 comes with `Retry-After`.
