API tokens
Create, scope, update and revoke the tokens behind Settings -> API tokens, and approve an OAuth client.
A token is personal: only its owner lists, edits or revokes it, an instance admin included. A bearer request sees only the token it is made with.
A token's effective power is the intersection of what it was granted and what its owner can still do in that team. Two capabilities decide where it acts at all: manage_tokens (the owner's tokens reach the team) and manage_mcp (they may drive it over MCP). Any member may mint a token; minting needs no capability of its own.
The scope is four optional lists on the same input (teamIds, projectIds, folderIds, appIds):
- Naming a node grants everything under it, now and later.
- Naming nothing means every team where the owner holds
manage_tokens, read live on each request. - Naming anything below a team narrows the token inside that team, and the team-wide permissions it holds (members, roles, registries, databases) stop applying there.
Reading
apiTokens
Any signed-in principal. Returns [ApiToken!].
Your own API tokens, whatever team they act in. A bearer request sees only the token it is made with.
query {
apiTokens {
id
name
prefix
capabilities
expiresAt
}
}Lifecycle
The raw secret comes back once, in createToken's payload. A token that names no capabilities is view-only.
createToken
Any signed-in principal. Returns CreateTokenPayload.
Create a personal API token with its own permissions. It reaches the teams where you hold manage_tokens and can never do more than you. The raw secret is returned once in the payload.
| Argument | Type | Description |
|---|---|---|
input | CreateTokenInput! | |
input.appIds | [String!] | |
input.capabilities | [Capability!] | |
input.expiresAt | String | |
input.folderIds | [String!] | |
input.instanceAdmin | Boolean | |
input.mcpAgent | String | The agent this token is for, as the connect wizard names it ("claude-code", "cursor", ...). |
input.name | String! | |
input.projectIds | [String!] | |
input.teamIds | [String!] |
mutation {
createToken(input: { name: "ci", capabilities: [deploy_apps, view_logs] }) {
raw
token { id prefix expiresAt }
}
}updateToken
Any signed-in principal. Returns Boolean.
Change one of your tokens' name, permissions or scope. The secret is unchanged, so tightening a live token costs no rotation. Returns true.
| Argument | Type | Description |
|---|---|---|
input | UpdateTokenInput! | |
input.appIds | [String!] | |
input.capabilities | [Capability!] | |
input.expiresAt | String | |
input.folderIds | [String!] | |
input.id | String! | |
input.instanceAdmin | Boolean | |
input.name | String! | |
input.projectIds | [String!] | |
input.teamIds | [String!] |
mutation {
updateToken(
input: { id: "tok_9f1c2ab7d3e4f5a6", name: "ci", capabilities: [deploy_apps] }
)
}revokeToken
Any signed-in principal. Returns Boolean.
Delete one of your tokens. The credential stops working immediately in every team it reached, and an OAuth connection's consent and refresh token go with it. Only its owner can. Returns true.
| Argument | Type | Description |
|---|---|---|
id | String! |
mutation {
revokeToken(id: "tok_9f1c2ab7d3e4f5a6")
}OAuth clients
A web AI client connects through OAuth, and the consent screen mints an ordinary token for it. This is the mutation behind that screen. Revoking the token disconnects the client everywhere it reached.
authorizeMcpClient
Any signed-in principal. Returns Boolean.
Approve an AI client: mint a personal API token with the chosen capabilities and scope, and link it to the client. It cannot grant more than the approver holds. The OAuth handshake itself is then finished by the browser posting to /api/auth/oauth2/consent - that endpoint refuses a server-side call, so it cannot be done here. teamIds names the teams the connection may work in, each gated on manage_mcp there; naming nothing means every team the approver may connect agents to, live.
| Argument | Type | Description |
|---|---|---|
appIds | [String!] | |
capabilities | [String!] | |
clientId | String! | |
expectedTeamId | String | The team the consent screen showed. Not a choice - the server takes the team from the session, but a disagreement is refused rather than connecting the client to a team nobody read. |
folderIds | [String!] | |
projectIds | [String!] | |
teamIds | [String!] |
mutation {
authorizeMcpClient(clientId: "oauth_client_9f1c2ab7", capabilities: ["view", "deploy_apps"], teamIds: ["team_9f1c2ab7d3e4f5a6"])
}See also
Did this page help you?