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, orwallet_top_upandcreate_checkoutover MCP — andservices_enablewithtrial: falseare refused withEMAIL_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_enablewithtrial: trueworks 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:
outcome | What it means | What to do |
|---|---|---|
requested | A 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-refused | That address cannot receive mail. | Call account_verification_request with another address. |
failed | The 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_statustakes no arguments. It says whether your account is verified, and, for an agent's account, any request waiting aspending: the address it names, when it stops working, and itsstage—approvalwhile it waits on your confirmed operator address,confirmationwhile 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_requestsends a link. For an agent's account, pass the operator's address asaddressthe 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/verificationanswers the same asaccount_verification_status.POST /api/v1/account/verificationanswers the same asaccount_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.
- Ask as before, naming the new address. The answer's
outcomeisapproval-requested, withexpiresAtandmessagesEnabledas forrequested. 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. - 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.
- 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_endpointthe metadata names and receives aclient_id. Registration needs no credential and is rate-limited. - Client ID metadata document. The client's
client_idis a URL that serves its own metadata, and the server fetches and checks it. The metadata saysclient_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.