# auth.md

You are an agent. BuildBase supports agentic registration: discover,
pick a method, register, work, and hand the claim to a person. Follow the
steps in order. The claim step stays human: never sign in, sign up or type a
claim code for the person.

## Step 1 - Discover

The BuildBase console's MCP server is https://api.console.buildbase.app/mcp. Without a
credential it answers 401 with a `WWW-Authenticate` header naming its
OAuth Protected Resource Metadata; that names the authorization server for a
person's sign-in (Step 3, service_auth).

## Step 2 - Pick a method

1. **You are a coding agent and the person has no BuildBase account, or
   you do not know** -> anonymous: a claimable organization. This is the
   fastest start: one request, no browser, and the person keeps it later by
   claiming it.
2. **You have the person, and they can sign in now (or sign up)** ->
   service_auth: they sign in on BuildBase's hosted page and approve
   you.
3. **identity_assertion (ID-JAG)** is not supported.

## Step 3 - Register

### anonymous

```http
POST https://central.console.buildbase.app/api/organizations/agent/identity
Content-Type: application/json

{ "type": "anonymous", "name": "Acme", "email": "person@example.com", "appName": "Acme web app", "app": { "redirectUrl": "http://localhost:3000/api/auth/callback" } }
```

`name` is required; `email` (the person's address, told when the
organization is claimed and shown on the claim page; never proof of
anything), `appName` and `app` are optional. With `app`, the same
request also turns on email-and-password and magic-link sign-in and makes
one sign-in client for your app with `app.redirectUrl` as its redirect URL:
an absolute `https://` URL, or `http://localhost` or `http://127.0.0.1`
with a port and path.

Response (201), returned once:

```json
{
  "success": true,
  "data": {
    "registrationType": "anonymous",
    "orgId": "<organization id>",
    "expiresAt": "<72 hours from now>",
    "apiKey": "<organization API key>",
    "apiKeyExpiresAt": "<the same>",
    "serverUrl": "<the organization's API base URL>",
    "mcpUrl": "https://api.console.buildbase.app/mcp",
    "claim": { "url": "<claim url>", "code": "<claim code>", "codeExpiresAt": "<15 minutes from now>" },
    "app": { "clientId": "<client id>", "clientSecret": "<client secret>", "redirectUrl": "<as sent>", "methods": ["password", "magic-link"] }
  }
}
```

`app` is there only when you sent `app`. Without it in the answer (an
older server), make the sign-in client yourself with the key. Put `apiKey`
and `app.clientSecret` in environment variables at once (for example
`.env.local`) and never repeat them in the chat: both are shown once. It works at once: send it as
`Authorization: Bearer <apiKey>` to `mcpUrl`, or to the admin API at
https://api.console.buildbase.app. Until a person claims it, the organization runs on these caps:
at most 50 end users, no custom domain, Stripe
test keys only, no invitations and no extra environments, and it and its key
are deleted after 72 hours. The only email it sends is sign-in
and verification mail (magic links, codes, password resets) to the address
each request names, in BuildBase's fixed wording ("a BuildBase test app (unclaimed)",
never your app's name or logo), at most 100
a day; campaigns, workflow and custom mail wait for the claim. The address
you gave, if any, gets two short notices from BuildBase only: when it
is claimed, and once before it is deleted. Use `serverUrl` from the answer
as the SDK's server URL. A refusal is JSON with a `code`:
`CLAIMABLE_CREATE_LIMIT` (this address, today), `CLAIMABLE_CREATE_CEILING`
(everyone, for now) or `CLAIMABLE_ORGS_DISABLED`. One address may create
3 a day. It starts on the plan
BuildBase gives every new organization; a paid plan or a trial is the
person's to choose in the console.

### service_auth

The person signs in, or signs up, on BuildBase's hosted page, with
their own sign-in method and second step, and approves you: OAuth 2.1 with
PKCE through the console MCP server's authorization server (Step 1). Ask for
the `console:read` scope, and `console:write` to change things. The hosted
page does not prefill an address from a `login_hint` yet. A command-line
tool may use the device code sign-in instead: https://console.buildbase.app/auth.md.

#### Registering your client

The console's MCP server (https://api.console.buildbase.app/mcp) is an OAuth 2.0 protected
resource: its metadata (RFC 9728) is at https://api.console.buildbase.app/.well-known/oauth-protected-resource/mcp.
This site's /.well-known/oauth-protected-resource names the same
authorization server and scopes, with this site as its resource. Its
authorization server is BuildBase's own, issuer `https://api.console.buildbase.app/org/6a2282bd2fc89b197be73364`, with
metadata (RFC 8414) at https://api.console.buildbase.app/.well-known/oauth-authorization-server/org/6a2282bd2fc89b197be73364 (a copy at
/.well-known/oauth-authorization-server).

1. Register your client:
   - **Dynamic client registration (RFC 7591).** `POST https://api.console.buildbase.app/api/v1/auth/oauth2/register?org=6a2282bd2fc89b197be73364`
     with `{ "client_name": "<your agent>", "redirect_uris": ["<your callback>"], "grant_types": ["authorization_code", "refresh_token"], "token_endpoint_auth_method": "none" }`.
     The answer carries your `client_id`. Register once per install and keep it.
2. Send the person to `https://api.console.buildbase.app/api/v1/auth/oauth2/authorize?org=6a2282bd2fc89b197be73364` with PKCE (S256),
   `resource=https://api.console.buildbase.app/mcp` and the scopes you need (`console:read`, and
   `console:write` to change things). They sign in, or sign up, and approve
   you.
3. Exchange the code at `https://api.console.buildbase.app/api/v1/auth/oauth2/token?org=6a2282bd2fc89b197be73364`, then call https://api.console.buildbase.app/mcp
   with `Authorization: Bearer <access_token>`. Refresh with the refresh
   token at the same endpoint.

## Step 4 - Claim ceremony (anonymous only)

When the person wants to keep the organization, give them the claim URL and
the claim code in one message:

> Open this link, sign in (or sign up), and enter this code: **<claim code>**
> <claim url>

The URL holds only an opaque id. The code lasts 15 minutes; for a
new one, call the `claim_link` tool on the MCP server, or:

```http
POST https://central.console.buildbase.app/api/organizations/agent/identity/claim-code
Authorization: Bearer <apiKey>
```

Once the person claims it, the organization is theirs and your key stops
working: connect again through service_auth, as them.

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| `CLAIMABLE_CREATE_LIMIT` (429) | create | This address made its 3 today. Use service_auth instead. |
| `UNCLAIMED_ORG_CAP` (403) | a capped change | It waits for the claim. Tell the person. |
| `CLAIM_EXPIRED` (409) | new claim code | Claimed already, or expired. Start again at Step 3. |
| 401 | with the key | The key ended: claimed or expired. Use service_auth. |

## Other credentials

- **Building an app for a developer who has an account:** an organization
  API key on the admin API at https://api.console.buildbase.app, made by a person in the console.
  Guide: https://docs.buildbase.app/auth.md.
- **Acting for a user of an app built on BuildBase:** the app is an
  OAuth 2.0 authorization server. Use OAuth with PKCE through the app's MCP
  endpoint; the user approves your access. Guide: https://docs.buildbase.app/auth.md.

Never ask a person for a password or a one-time code, and never ask them to
paste a token into a chat.
