# auth.md

You are an agent. Scribe (`getscribe.ai`) is an **OAuth 2.1 / OIDC
authorization server**. Authenticate, then call the Scribe API — or, when
available, the **MCP server** at `https://getscribe.ai/api/mcp`.

`https://getscribe.ai` is both the resource server (the API you call) and the
authorization server (`issuer` = `https://getscribe.ai/api/auth`).

Three ways to get a credential, by client type:

- **Interactive agents** (ChatGPT, Claude, Cursor, …) → **OAuth 2.1
  authorization code + PKCE**, with **dynamic client registration** so you can
  self-register. This is the path to the MCP server. (Steps 1–4.)
- **Desktop apps / CLIs** → **device authorization** (RFC 8628). (Step 5.)
- **Headless ingestion** (CI, build servers) → **API key**. (Step 6.)

## Step 0 — Eligibility (read first)

Scribe is in **invite-only private beta**. Authentication only issues
credentials for users who **already have a Scribe account** — it does not
create accounts.

- User has an account → continue.
- User has no account → they can't be onboarded through you yet. Send them to
  `https://getscribe.ai/waitlist`. Once admitted and signed in once in a browser,
  return here. An agent cannot complete sign-up on a user's behalf.

Start at `https://getscribe.ai/llms.txt` for a map of agent-readable pages.

## Step 1 — Discover

A `401` from the API (or the MCP endpoint) carries a `WWW-Authenticate`
header pointing at the Protected Resource Metadata (RFC 9728):

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://getscribe.ai/.well-known/oauth-protected-resource"
```

Fetch the PRM, then the Authorization Server Metadata (RFC 8414) it points to:

```http
GET /.well-known/oauth-protected-resource          # resource + authorization_servers + scopes
GET /.well-known/oauth-authorization-server        # the OAuth endpoints below
```

The AS metadata gives you everything:

- `authorization_endpoint` — `https://getscribe.ai/api/auth/oauth2/authorize`
- `token_endpoint` — `https://getscribe.ai/api/auth/oauth2/token`
- `registration_endpoint` — `https://getscribe.ai/api/auth/oauth2/register`
- `jwks_uri` — `https://getscribe.ai/api/auth/jwks` (verify access tokens here)
- `code_challenge_methods_supported` — `["S256"]` (PKCE required)
- `scopes_supported` — `openid profile email offline_access` plus the
  resource scopes: `timeline:read`, `timeline:write`, `projects:read`,
  `projects:write`, `invoices:read`, `invoices:write`, `teams:read`
  (list the workspaces the caller belongs to; independent of `projects:read`).

## Step 2 — Register (dynamic client registration)

Public clients (no secret) self-register with PKCE:

```http
POST /api/auth/oauth2/register
Content-Type: application/json

{
  "client_name": "Your Agent",
  "redirect_uris": ["https://your-agent.example/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}
```

Response includes your `client_id`. Most MCP hosts do this for you.

## Step 3 — Authorize + exchange (authorization code + PKCE)

Send the user to `authorization_endpoint` with your `client_id`, the scopes
you need, a PKCE `code_challenge` (S256), and `resource` set to what you'll
call (`https://getscribe.ai/api/mcp` for MCP, or `https://getscribe.ai/api` for the REST API).
The user signs in and approves the requested scopes on the consent screen.
First-party clients may skip consent.

Exchange the returned `code` at `token_endpoint` with your PKCE
`code_verifier` for an `access_token` (a JWKS-verifiable JWT) and, if you
requested `offline_access`, a `refresh_token`.

## Step 4 — Call the API or MCP

Present the access token as a bearer on every request:

```http
POST /api/mcp                      # MCP (Streamable HTTP), when enabled
Authorization: Bearer <access_token>
```

**The MCP server is the agent surface.** Fetch
`https://getscribe.ai/.well-known/mcp/server-card.json`. If it returns the server card
(200), connect your MCP client to `https://getscribe.ai/api/mcp`; its tools are
scope-gated by your granted scopes. If it returns 404, MCP is not enabled in
this environment and there is no OAuth-accessible API here yet — stop and check
back later.

(The `/api/*` REST routes you may see referenced in `https://getscribe.ai/openapi.json`
are the Scribe app's own session-authenticated interface. They do **not**
accept OAuth access tokens — use the MCP server, not raw REST, as an agent.)

When the access token expires, refresh it (if you have a refresh token) or
re-run Step 3. On a `401` for a previously-working token, drop it and restart
at Step 1.

## Step 5 — Desktop / CLI: device authorization (RFC 8628)

For clients that can't host a browser redirect:

```http
POST /api/auth/device/code        { "client_id": "scribe-mobile" }
# → user_code + verification_uri (https://getscribe.ai/device); user approves in a browser
POST /api/auth/device/token       { "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": "...", "client_id": "scribe-mobile" }
# → poll at the returned interval; 400 authorization_pending until approved, then access_token
```

`client_id` must be a registered value (`scribe-cli`, `scribe-desktop`, `scribe-mobile`).

## Step 6 — Headless: API key

API keys (`scribe_…`) are minted from `https://getscribe.ai/settings` by an org member —
non-expiring, attributed to a person, no anonymous self-service. Present them
as a bearer token exactly like an access token.

## Errors

| HTTP | meaning | action |
|---|---|---|
| 401 + WWW-Authenticate | not authenticated | follow the PRM from Step 1 |
| 403 `no_organization` | user has no workspace | user creates one at the hub, then retry |
| 400 `authorization_pending` | device flow not approved yet | keep polling at the interval |
| 400 `invalid_grant` | bad/expired code or device_code | restart the relevant flow |
| 429 | rate limited | exponential backoff |
| 5xx | transient | retry with backoff |

## Revocation

You don't initiate revocation. A user can revoke consent, sign out the
session, or delete the API key from `https://getscribe.ai/settings`; the OAuth provider
can revoke tokens at the revocation endpoint. You find out on the next call
returning `401` — drop the credential and restart at Step 1.
