Create an agent principal and the confidential OAuth client it authenticates as.
Request
- The document declares no server URL.
- Auth: one of:
- HTTP bearer
- API key in cookie __Secure-better-auth.session_token
Request body
Only agents are created here. Humans become principals by joining the organisation — see the invitation operations — so a human principal can never be typed into existence.
Authority over the ESTATE. org.read lets the agent see the organisation's mailboxes, addresses and routing; org.manage lets it create them.
⛔ org.manage never issues a GRANT and never raises a ceiling — an agent that can grant can mint another agent and promote it, and a ceiling that can raise itself is not a ceiling.
⚠️ It MAY create one over MCP — create_agent — because creating a principal confers nothing: the row holds no grant and no ceiling, so its credential is refused at issuance until a human acts. The tool was withdrawn for a day when minting went through Better Auth's admin API AS THE CALLER and an MCP request carries no session; auth now selects a session-free adapter for that caller, registering through the provider's own RFC 7591 endpoint. ⛔ The new agent's ceiling is a hard zero — an agent that could pass one through would be minting a peer that can provision immediately, and by induction a lineage of them.
Omitted means none, which is what an agent that only needs a mailbox should have.
The loopback port a LOCALLY-RUNNING agent will listen on for its OAuth callback.
Omitted — the default — mints the machine-to-machine client this endpoint has always minted: client_credentials only, no redirect URIs, because there is no human to redirect. Supplied, the client additionally gets authorization_code, so a human can authorise it in a browser and the agent acts as that human rather than as a bare machine.
⛔ A PORT, not a URI, and that is a security boundary rather than a convenience. The server owns the URI shape; a caller-supplied redirect is precisely the field an open-redirect lives in. Two are registered from this number — http://localhost:<port>/callback and the portless http://127.0.0.1/callback — and nothing else is reachable through this endpoint.
⚠️ Why the port has to be pinned at all. RFC 8252 §7.3 grants port variance to loopback IP literals only, so the 127.0.0.1 entry matches on any port for free. localhost is a DNS name and §8.3 advises against it, so it matches by exact string — and Claude Code publishes a portless http://localhost/callback and then presents localhost:<port>, which is why it cannot use CIMD against this server at all. Registering the exact port is what makes such a client work, and it is why the client must be told the same number (--callback-port). See packages/auth/README.md and anthropics/claude-code#42765.
Response
The request has succeeded and a new resource has been created as a result.
What the org calls this principal. Display and audit only.
Humans: the member's user id. Null for agents — nobody is behind one.
Agents: the OAuth client this principal IS. Null for humans.
Minted with the principal, not claimed later: the authorization server holds a per-client capability ceiling and refuses to issue against an empty one, so the client cannot reach a mailbox before a grant says it may. That server-owned ceiling is what the one-time enrolment code used to stand in for.
Agents are enrolled from birth (→ revoked); humans are active (→ revoked).
What this principal may do to the ESTATE, as opposed to what it may do with a mailbox.
Empty for every human — a person's authority over the organisation is their membership ROLE, resolved per request, and they never authenticate as an OAuth client. For an agent it is the ceiling an admin set at creation, and it is published because an agent that can create mailboxes is an agent that can spend this organisation's capacity: that should be visible on the list, not buried in a token.
Agents: the member who minted it. Null for humans.
Always null. Kept so a client reading this field does not break; nothing sets it.
The client id to authenticate as. Also on oauthClientId, which is not secret.
The client secret. Shown once and never again.
Changes
No recorded changes to this operation across all 1 revision of this API.