Skip to content

Authentication

Organisations and principals

Every caller is a principal acting for an organisation. A principal is a person or an agent. The organisation owns the keys, the credit and the data, and every call is answered for the caller's own organisation. No call takes another organisation's id to act on it.

A person's organisation is created when they sign up. An agent's is created when it signs up for a key. People hold a role in their organisation (owner, admin or member), and the role decides what they may change.

A person: session and passkey

A person signs up on the sign-up page with a name, an email and a password. Signing up does not sign you in: the page asks you to check your inbox, and you sign in with the same email and password. A session cookie then carries your identity. Signing out ends the session. You can add a passkey and sign in with it instead of a password.

A session is how the dashboard calls the REST API. Some endpoints answer a signed-in person only; an agent reaches the same things over MCP.

Verifying your address. Signing up sends a link to your address. Open it, enter your password on the page it opens, and confirm. Nothing happens just by opening the link, and the link works only for your own account. You can sign in before you verify, but buying credit and turning a service on in full need a verified address, and any free credit your account gets arrives when you verify. Trials work without it. Lost the message? The dashboard sends a new link, as does POST /api/v1/account/verification.

If the address you sign up with already has an account, the answer is the same as for a new one, and the message to that address says how to sign in instead.

Forgotten your password? The sign-in page links to a page that mails you a reset link. The link opens a page that asks for your new password twice, and whether to remove your account's passkeys too. A reset ends every session your account has, everywhere; your organisation's API keys are not touched. Resetting does not verify your address. If this deployment has reset messages switched off, the page says so and sends nothing.

An agent: API key

An agent presents an API key on the REST API in one header:

Authorization: Bearer <apiKey>

The key belongs to the organisation, not to one agent. An API key may rotate or revoke the key it presents, and no other: POST /api/v1/keys/{keyId}/rotate and DELETE /api/v1/keys/{keyId} with the key's own keyId. Any other key id is answered 404. Rotating returns the new key once and cancels the one you presented, so keep the answer. An organisation's keys can rotate themselves 10 times an hour; beyond that the answer is 429 with Retry-After. If your key was cancelled while you were rotating it, the rotation is answered 404 and no new key is issued. Listing keys, and managing any key but your own, still needs a person signed in to the organisation whose role allows it, on the dashboard's Keys page or with their session on the REST API (see the API); GET /api/v1/keys with an API key is refused with 401. A key that is present but unknown, revoked or expired is refused with 401. It never falls back to a session, even when the request also carries a session cookie.

The REST API does not accept an OAuth access token. An API key is not accepted at /mcp: an MCP client uses the OAuth door below.

Keyless agent sign-up

POST /api/v1/signup with no credential creates an organisation and returns its first API key, once. The request, the answer and the limits are in Getting started.

Why sign-up asks for your operator's email

Send your human's email address as operatorEmail when you sign up. That person is your operator, and their address is how your account is verified:

  • Any free credit is released when the operator confirms the address. A new account's wallet starts empty. Sign-up mails your operator a link; they open it and press Confirm, and any free credit arrives in your wallet then — once per account.
  • Buying credit and turning a service on in full need a verified address. Until the operator confirms, buying credit — POST /api/v1/wallet/top-ups, or wallet_top_up and create_checkout over MCP — and services_enable with trial: false are refused with EMAIL_VERIFICATION_REQUIRED. The refusal names where to verify: a page (verifyUrl), the tool (verifyTool) and the REST route (verifyRest). Trials do not need it: services_enable with trial: true works from the first call.

The operator needs no account here, and their address never becomes a way to sign in to yours. Your key stays the only credential your account has.

operatorEmail is required. A sign-up without one is refused with 400 and a validation error, and no account is created. A confirmed operator address is also how a lost key is replaced: see If you lose your key below.

What became of the address

The sign-up answer carries verification, whose outcome says what happened to the address you sent:

outcomeWhat it meansWhat to do
requestedA link was issued. expiresAt is when it stops working. If messagesEnabled is false, this deployment has verification messages switched off and nothing was sent.Ask your operator to open the link and press Confirm.
address-refusedThat address cannot receive mail.Call account_verification_request with another address.
failedThe platform could not send the link this time.Call account_verification_request again.

Whatever the outcome, the sign-up itself succeeded: keep the key. An operatorEmail that is not a valid email address (or not a string) refuses the whole sign-up instead, with 400 and a validation error, and no account is created.

The account tools and routes

Over MCP:

  • account_verification_status takes no arguments. It says whether your account is verified, and, for an agent's account, any request waiting as pending: the address it names, when it stops working, and its stage — approval while it waits on your confirmed operator address, confirmation while it waits on the address named. Once the account is verified, reading it also delivers any free credit that is due but has not arrived.
  • account_verification_request sends a link. For an agent's account, pass the operator's address as address the first time; a later call without one sends a fresh link for the request still waiting, to whichever address it is waiting on. For a person's account, pass no address: the link goes to the address they sign in with.

Over the REST API, with your key or a person's session:

  • GET /api/v1/account/verification answers the same as account_verification_status.
  • POST /api/v1/account/verification answers the same as account_verification_request; its optional body is { "address": "…" }.

Each organisation has one hourly allowance of verification requests, shared by the tool and the route; past it the answer is 429 with Retry-After. For an agent's account a new link replaces the one before it, so only the newest works. Any free credit is released only once.

Changing a confirmed operator address

Once your operator has confirmed an address, a different one replaces it only with that operator's approval. Your key is all it takes to ask, so without this anyone holding your key could move key recovery to an inbox of their own.

  1. Ask as before, naming the new address. The answer's outcome is approval-requested, with expiresAt and messagesEnabled as for requested. A link goes to the confirmed address, not the new one; the message names the new address and asks the operator to approve. Nothing changes yet, and nothing is sent to the new address.
  2. The operator opens the link and presses Approve. The page does not show the new address — the message already named it — and nothing happens just by opening the link.
  3. The new address is then sent its own confirmation link. Once its owner presses Confirm, it replaces the old address, and only it can recover your key from then on.

account_verification_status shows the request with stage approval until step 2 and confirmation after it, while the confirmed address stays the current one. Asking again without an address, while the request has not expired, re-sends the link for the stage it has reached. Once the new address has its confirmation link, asking again sends a fresh one but does not extend the request: it still ends at the expiry set when your operator approved, which the status shows. A request that has lapsed starts again at approval, so your confirmed operator is asked again; naming yet another address also starts again at approval. There is no way to decline: an operator who does not approve lets the request lapse when its link expires, and the confirmed address stays. The message also tells an operator who did not ask that someone else may hold your key, and links the key-recovery page, where every key can be cancelled; completing a recovery also cancels any change still waiting.

If your operator no longer has the confirmed mailbox, they cannot approve: contact platform support to have the address changed.

If you lose your key

Your operator can get your organisation a new key, if your account's operator address has been confirmed. Ask with no credential:

POST /api/v1/account/key-recovery
Content-Type: application/json

{ "address": "<the confirmed operator address>" }

The answer is 202 with { "accepted": true } whatever the address is and whatever it operates, so the answer tells nobody whether an address has an account here. Before that, too many requests from your network address are refused with 429 and Retry-After, a body that is too large with 413, and an address that is not a valid email address with 400.

If the address is the confirmed operator address of any agent account, it is sent one message with a link for each organisation it operates, labelled with the organisation's name, or with its id when the name is missing, too long or looks like a web address. Opening a link changes nothing. The page it opens has an Issue a new key button; pressing it issues that organisation a new key, shows it once, and cancels every key the organisation had before, and any change of operator address still waiting for approval or confirmation. The new key is never sent by email. Each link works once and for an hour, a newer request replaces it, and it stops working if the operator address changes.

Recovery needs a confirmed operator address. An account made before sign-up required one, or whose operator never confirmed it, cannot be recovered: name an address and have your operator confirm it while you still hold your key, because naming one needs the key.

No MCP tool starts a recovery: a caller that has lost its key cannot reach one. Recovery messages can be switched off on a deployment; the recovery page says so when they are.

The OAuth 2.1 authorization server

MCP clients authenticate through an OAuth 2.1 authorization server. It runs the authorization-code grant with PKCE (S256) and issues refresh tokens. Access tokens are signed JWTs, checked against the published key set.

The discovery documents:

  • /.well-known/oauth-authorization-server: the authorization server's metadata (RFC 8414). It names the authorization, token and registration endpoints and the key set.
  • /.well-known/openid-configuration: OpenID Connect discovery.
  • /.well-known/oauth-protected-resource/mcp: the MCP endpoint's protected resource metadata (RFC 9728). It names the resource and the authorization server that issues tokens for it.

A client identifies itself in one of two ways:

  • Dynamic client registration (RFC 7591). The client registers at the registration_endpoint the metadata names and receives a client_id. Registration needs no credential and is rate-limited.
  • Client ID metadata document. The client's client_id is a URL that serves its own metadata, and the server fetches and checks it. The metadata says client_id_metadata_document_supported, so a client that knows this form can use it.

A person then signs in, or signs up if they have no account, and sees a consent page naming the client and the scopes it asks for. Approving it lets the client get its token. How a client runs the flow is in MCP.

Where it is enforced

Every REST route and every MCP tool checks the caller itself, beside the data it serves. The only header read as a credential is Authorization; a person's session travels in its cookie. The documentation pages are public and check nothing.