# auth.md

How a machine gets a credential for reachpad, which publishes the apps a
coding agent builds and serves each one at a link. Two surfaces call it, one
account is behind both, and one access token works on both.

| Surface | Endpoint | Credential |
| --- | --- | --- |
| Hosted MCP server | https://reachpad.dev/mcp | OAuth 2.1 access token |
| REST API | https://reachpad.dev/api/apps | The same access token, as a bearer |

## Hosted MCP server

The endpoint is
https://reachpad.dev/mcp
POST only, streamable HTTP, JSON-RPC. GET answers 405: no server-initiated
stream exists, because every tool in this surface is request and response.

Reachpad is an OAuth resource server, not an authorization server. Register
yourself, with no support ticket and no pre-shared client id:

1. Read the protected resource metadata (RFC 9728) at
   https://reachpad.dev/.well-known/oauth-protected-resource
   A call with no token also gets that URL, as the `resource_metadata`
   parameter of the `WWW-Authenticate` challenge on the 401.
2. Read the authorization server metadata it names, at
   https://splendid-experience-99.authkit.app/.well-known/oauth-authorization-server
   Take `registration_endpoint`, `authorization_endpoint` and
   `token_endpoint` from that document rather than from this page: they are
   the authorization server's to change.
3. Register with RFC 7591 dynamic client registration at the
   `registration_endpoint`. Authorization code with PKCE (`S256`) is the
   supported grant, and `offline_access` gets you a refresh token.
4. Send this exact resource parameter on the authorization and token requests:
   resource=https://reachpad.dev/mcp
   The token's audience is checked against it, so a token minted for another
   resource, or for another reachpad deployment, is refused with 401.
5. Call with `Authorization: Bearer <access token>`.

Scopes: `openid`, `profile`, `email`, `offline_access`.

`initialize`, `ping` and `tools/list` answer with no credential, so a
client can describe reachpad before anybody has signed in. `tools/call` is
the method that reaches an account, and it is the one that needs the token.

Every call is made as the person who approved the client. It reaches the apps
that person can reach and no others, and the organization it writes into is
the one their account has current at reachpad.dev, never one named in the call.
`whoami` reports it, and every other organization the account is in.

The account's email address has to be verified before any tool that reads or
writes apps will answer, because an invitation to an app names a mailbox and
nothing else. A call from an unverified account is refused with
`email_unverified`.

## What is not here

Two things a reader of the authorization server's metadata will look for and
not find. Both were checked against it on 17 September 2026.

There is no agent-auth profile. The metadata carries no `agent_auth` block
and names no identity, claim or events endpoint, so there is no identity
assertion and no claim ceremony to call. Dynamic client registration above is
the whole registration story.

The device code grant is advertised and refused. The metadata lists
`urn:ietf:params:oauth:grant-type:device_code` under
`grant_types_supported` and names a `device_authorization_endpoint`,
but a client registered the way this page tells you to register cannot use
either: registration rejects that grant type with `invalid_client_metadata`,
accepting only `authorization_code` and `refresh_token`, and the device
endpoint answers `unauthorized_client`, device authorization is not enabled
for this application. Authorization code with PKCE is the only grant that
works here.

So a client with no browser and no callback host cannot finish on its own
today. The consent screen is a real click and it needs the account-holder.

## REST API

The base is
https://reachpad.dev/api/apps
with `Authorization: Bearer <access token>`, the same token the MCP flow
above mints. The MCP tools and these routes call one service, so a role check
answers the same either way.

- `GET /api/apps/me` names the caller and the organization they resolved to.
- `GET /api/apps` lists apps; `POST /api/apps` publishes a new one.
- `GET /api/apps/{id}` reads one, with its live version and file listing.
- `POST /api/apps/{id}/versions` publishes a new version, and
  `POST /api/apps/{id}/versions/{number}/promote` makes an earlier one live.
- `PUT /api/apps/{id}/access` sets restricted, org_link or public_link.
- `POST /api/apps/{id}/shares` invites one email address as a viewer or an
  editor.

For REST create and version requests, each file takes `{ path, content, encoding }`.
Set `encoding: "utf8"` for plain text, for example:

```json
{ "name": "Hello", "files": [{ "path": "index.html", "content": "<h1>Hello</h1>", "encoding": "utf8" }] }
```

Omitting `encoding` means `base64`, for compatibility with existing clients.
Encode binary files as base64. Malformed base64 is refused before storage.
MCP tools accept plain text content and encode it for you; do not base64-encode
content passed to `create_app` or `update_app`.

A refusal is JSON carrying a code, and the codes are the ones the tools use:
`unauthorized`, `account_unknown`, `forbidden`, `email_unverified`, `not_found`,
`invalid_request`, `slug_taken`, `stale_base`, `not_empty`,
`too_large` and `rate_limited`.

## More

- Catalog of the callable surfaces: https://reachpad.dev/.well-known/api-catalog
- MCP server card: https://reachpad.dev/.well-known/mcp/server-card.json
- Agent skill index: https://reachpad.dev/.well-known/agent-skills/index.json
- Product manual: https://reachpad.dev/docs
- Publishing, in markdown: https://reachpad.dev/docs/publishing.md
