# sOURnd auth.md

You are an agent. This page tells you how to sign in to sOURnd for a user, so you can list and download the engines they own. It follows the auth.md convention (https://workos.com/auth-md) in its `register_uri` / `claim_uri` form, on standard OAuth 2.1.

Most of sOURnd needs no sign-in: search, compare, prices, integration code and checkout links work without a token, on the MCP server (https://sournd.com/mcp) and the REST API (https://sournd.com/api/v1). Sign in only to act for a buyer: `list_my_engines` and `get_download_link` on MCP, or `GET /api/v1/me/engines` and `GET /api/v1/me/engines/{slug}/download`.

**A token can never pay.** There is no payment scope. Buying always needs the user to approve the purchase at PayPal (`create_checkout` gives you the link to hand them).

## Discover

1. Call a protected tool or endpoint without a token. The answer is `401` with:

   ```
   WWW-Authenticate: Bearer resource_metadata="https://sournd.com/.well-known/oauth-protected-resource/mcp", scope="engines:read"
   ```

2. `GET` that document (RFC 9728). Resources: `https://sournd.com/mcp` (MCP), `https://sournd.com/api/v1` (REST) and `https://sournd.com` (both). Each names the authorisation server `https://sournd.com`.
3. `GET https://sournd.com/.well-known/oauth-authorization-server` (RFC 8414) for the endpoints and the `agent_auth` block:

   ```json
   { "issuer": "https://sournd.com",
     "agent_auth": {
       "skill": "https://sournd.com/auth.md",
       "register_uri": "https://sournd.com/oauth/register",
       "claim_uri": "https://sournd.com/oauth/authorize",
       "identity_types_supported": ["anonymous"],
       "anonymous": { "credential_types_supported": ["access_token", "refresh_token"],
                      "claim_uri": "https://sournd.com/oauth/authorize" } } }
   ```

## Pick a method

`identity_types_supported` is **`anonymous`**: you register without asserting anyone's identity, then the user claims the registration by approving you in their browser. `identity_assertion` (ID-JAG) and `service_auth` are not supported. The credentials are an OAuth `access_token` (Bearer, one hour) and a rotating `refresh_token` (30 days).

## Register

Register once with RFC 7591 dynamic client registration. No authentication.

```http
POST https://sournd.com/oauth/register
Content-Type: application/json

{ "client_name": "Your agent",
  "redirect_uris": ["http://127.0.0.1:33418/callback"] }
```

Response `201`: `client_id`, `token_endpoint_auth_method: "none"` (a public client: no secret). Redirect URIs must be `https`, `http` on a loopback address (`localhost`, `127.0.0.1`, `[::1]`, any port) or a private-use scheme such as `com.example.app:/callback`.

## Claim

Send the user to the authorise endpoint with PKCE (S256 only):

```
https://sournd.com/oauth/authorize?response_type=code&client_id=<client_id>&redirect_uri=<uri>&scope=engines:read%20engines:download%20builds:record&state=<random>&code_challenge=<BASE64URL(SHA256(verifier))>&code_challenge_method=S256&resource=https://sournd.com/mcp
```

Tell the user to open it. They enter the email they bought with, open the sign-in link we email them (on the same device), and see "Allow <your client_name> to see and download the engines you own?" with Allow and Deny. Allow sends them to your `redirect_uri` with `code`, `state` and `iss=https://sournd.com`; Deny sends `error=access_denied`. The code lasts five minutes and works once.

Scopes:

- `engines:read`: list the engines, licences and plans on the user's email.
- `engines:download`: get a short download link for their engine library.
- `builds:record`: record the plugins and games you build with their engines on their build register (`record_build`). Voluntary; nothing is owed.

Asking for no scope asks for all three. `offline_access` is accepted and ignored: you always get a refresh token.

## Exchange

```http
POST https://sournd.com/oauth/token
Content-Type: application/x-www-form-urlencoded

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

Response: `access_token`, `token_type: "Bearer"`, `expires_in: 3600`, `refresh_token`, `scope`. Refresh with `grant_type=refresh_token&refresh_token=<token>&client_id=<client_id>`; each refresh token works once and the response carries a new one. Using a code or a spent refresh token twice revokes the whole sign-in.

## Use

Send the access token in the header only:

```http
POST https://sournd.com/mcp
Authorization: Bearer <access_token>
```

- `list_my_engines` (`GET /api/v1/me/engines`): engines with `access` (`licence` or `plan`), plans, and whether the library download is ready.
- `get_download_link` (`GET /api/v1/me/engines/<slug>/download`): a signed link, valid ten minutes, to one zip of the whole library. Add `platform` (`macos-universal`, the default, `linux` or `windows-x64`; as a query parameter on REST) for the machine you build on: each platform is its own build. If the user has not prepared the download for that platform yet, you get `not_prepared` and `prepare_url`: ask them to open it, pick the platform and press "Download selected". They must confirm it themselves, because starting a download ends their 14-day right to cancel. Download links are rate limited.

`403` with `error="insufficient_scope"` means the token lacks the scope named in `WWW-Authenticate`: sign in again asking for it. `401` with `error="invalid_token"` means refresh, or sign in again.

## Revoke

```http
POST https://sournd.com/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=<access_or_refresh_token>&client_id=<client_id>
```

Always `200`. Revoking a refresh token ends the whole sign-in. The user can also disconnect you from their engines page (https://sournd.com/library), and "Sign out everywhere" disconnects every agent.

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| `invalid_redirect_uri` | `/oauth/register` | Use an allowed redirect URI. |
| `invalid_client` | `/oauth/token` | Unknown `client_id`: register again. |
| `invalid_grant` | `/oauth/token` | Code or refresh token expired, used or revoked: start the claim again. |
| `invalid_scope` | authorise, token | Ask only for `engines:read`, `engines:download` and `builds:record`. |
| `access_denied` | your redirect URI | The user said no. Do not retry unasked. |
| `insufficient_scope` (403) | MCP, API | Sign in again with the scope named. |
| `429` | any | Back off and retry later. |
