Teams, keys and tokens API
Lists the API routes for teams, members, invitations, SSH keys, signing keys, and tokens.
Last updated
On this page
- Teams
- The team document
- POST /v1/teams
- GET /v1/teams
- GET /v1/teams/{slug}
- PATCH /v1/teams/{slug}
- GET /v1/teams/{slug}/profile
- DELETE /v1/teams/{slug}
- PATCH /v1/teams/{slug}/members/{email}
- DELETE /v1/teams/{slug}/members/{email}
- POST /v1/teams/{slug}/members/{email}/pause
- POST /v1/teams/{slug}/members/{email}/unpause
- POST /v1/teams/{slug}/invites
- DELETE /v1/teams/{slug}/invites/{id}
- GET /v1/invites/{token}
- POST /v1/invites/{token}/accept
- GET /v1/teams/{slug}/removals
- POST /v1/teams/{slug}/members/{email}/delete-now
- Keys
- POST /v1/keys
- GET /v1/keys
- DELETE /v1/keys/{*id}
- Tokens
- POST /v1/tokens
- GET /v1/tokens
- DELETE /v1/tokens/{id}
- Using a token
- CLI login
- POST /v1/cli/code
- GET /v1/cli/code/{code}
- POST /v1/cli/approve
- GET /v1/cli/token
- GET /v1/cli/tokens
- DELETE /v1/cli/tokens/{id}
Teams
The slug of a team is its handle. It has 3 to 39 lowercase letters, digits, and -, and does not start or end with -. It is not reserved and never equals a person's handle. The roles member, admin, and owner and their rights are in Roles. The last owner cannot step down or be removed.
A non-member gets 404 no such team on every route. A paused member gets 403 your access to this team is paused. A member below the role a route needs gets 403 your role does not allow that.
The team document
GET /v1/teams/{slug} returns this. A field marked "admins" is empty for a member.
{ "slug": "acme", "name": "Acme", "description": "The platform team", "createdAt": "2026-09-01T08:00:00Z", "public": true, "tagline": "We build the build farm", "location": "Pune", "website": "https://acme.example", "email": "[email protected]", "yourRole": "owner", "members": [ { "email": "[email protected]", "name": "Karthik", "username": "karthik", "role": "owner", "joinedAt": "2026-09-01T08:00:00Z", "state": "active" } ], "invites": [ { "id": "5d41402abc4b2a76b9719d911017c592a1b2c3d4e5f60718293a4b5c6d7e8f90", "email": "[email protected]", "role": "member", "invitedBy": "[email protected]", "expiresAt": "2026-10-25T08:00:00Z" } ]}| Field | Type | Meaning |
|---|---|---|
slug, name, description | string | the handle, the display name, and a free description |
createdAt | string | RFC 3339 |
public, tagline, location, website, email | the public profile; see GET /v1/teams/{slug}/profile | |
yourRole | string | your role, so a client can decide what to offer. The server refuses anything the role does not allow regardless |
members | object[] | email, name, username (omitted for someone who never picked a handle), role, joinedAt (RFC 3339), state (active or paused) |
invites | object[] | open invitations: id, email, role, invitedBy, expiresAt. Empty unless you are an admin or owner |
POST /v1/teams
Makes a team and makes you its owner. Takes a desk.
| Field | Type | Required | Rule |
|---|---|---|---|
slug | string | yes | The handle rule above. Trimmed |
name | string | yes | Not empty |
region | string | yes | An active region from GET /v1/regions. A team is placed in one region when it is made; no later call changes it except a region request |
{ "slug": "acme", "name": "Acme", "region": "centralindia" }Response: 201, the team as stored.
{ "_id": "acme", "name": "Acme", "description": "", "public": false, "tagline": "", "location": "", "website": "", "email": "", "region": "centralindia", "createdBy": "[email protected]", "createdAt": { "$date": { "$numberLong": "1760400000000" } }, "members": [ { "user": "[email protected]", "role": "owner", "joinedAt": { "$date": { "$numberLong": "1760400000000" } }, "state": "active" } ]}Errors:
400— the handle is refused, with the rule:handle must be at least 3 characters,handle must be 39 characters or fewer,handle must be lowercase,handle may use letters, digits and dashes only,handle may not start or end with a dash,that handle is reserved,that handle cannot be used; orteam name required.409—handle already taken, by a team or a person.422—region required;unknown region: {region}.502—could not read regions, or the API cannot write the team.503—regions unavailable.
GET /v1/teams
Lists the teams you belong to, including the ones where you are paused. Takes a desk or a desk tool token.
Response: 200, an array of teams as stored (the shape in POST /v1/teams), each with state, your own state in it.
[ { "_id": "acme", "name": "Acme", "region": "centralindia", "createdBy": "[email protected]", "createdAt": { "$date": { "$numberLong": "1760400000000" } }, "members": [{ "user": "[email protected]", "role": "owner", "joinedAt": { "$date": { "$numberLong": "1760400000000" } }, "state": "active" }], "public": false, "description": "", "tagline": "", "location": "", "website": "", "email": "", "state": "active" }]GET /v1/teams/{slug}
Reads a team. Any member can; takes a desk or a desk tool token.
Response: 200, the team document.
Errors:
403—your access to this team is paused.404—no such team.
PATCH /v1/teams/{slug}
Changes the name and description, and optionally the public profile. Any member can change the name and description; the profile takes an admin or owner. Takes a desk.
| Field | Type | Required | Rule |
|---|---|---|---|
name | string | yes | Not empty |
description | string | no | Empty clears it |
profile | object | no | Admins and owners only. The whole profile, not a patch: a field you leave out is cleared |
profile fields, all optional:
| Field | Type | Rule |
|---|---|---|
public | boolean | true lets strangers read the profile. Default false |
tagline, location, email | string | |
website | string | Empty, or an http:// or https:// URL of at most 2048 characters. Any other scheme is refused |
{ "name": "Acme", "description": "The platform team", "profile": { "public": true, "tagline": "We build the build farm", "website": "https://acme.example" } }Response: 204. A refused profile changes nothing, not even the name.
Errors:
400— awebsitethat is nothttp://orhttps://(website must start with http:// or https://) or is too long;team name required.403—owner or admin only, for amemberwho sentprofile;your access to this team is paused.404—no such team.
GET /v1/teams/{slug}/profile
The team's public face. Anonymous: it needs no token. A team that has not set public is 404 no such team, the same as a team that does not exist.
Response: 200.
{ "slug": "acme", "name": "Acme", "description": "The platform team", "tagline": "We build the build farm", "location": "Pune", "website": "https://acme.example", "email": "[email protected]", "memberCount": 4}DELETE /v1/teams/{slug}
Deletes a team. Owners only. Takes a desk. Takes no body.
Response: 204.
Errors:
403—your role does not allow that.404—no such team.
PATCH /v1/teams/{slug}/members/{email}
Changes a member's role. {email} is the member's email. An admin can make anyone a member or admin, and cannot change an owner. Only an owner can make anyone an owner or change an owner's role. Takes a desk.
| Field | Type | Required | Rule |
|---|---|---|---|
role | string | yes | member, admin, or owner |
{ "role": "admin" }Response: 204.
Errors:
403—your role does not allow that change;your role does not allow that.404—not a member;no such team.409—a team must keep at least one owner.
DELETE /v1/teams/{slug}/members/{email}
Removes a member, or lets a member leave. Takes no body and a desk.
Anyone can remove themselves. Removing someone else takes admin for a member or admin, and owner for an owner. The removed person's keys stop being admitted to the team at once. Their team workspaces and desks are marked for deletion by the next key sync, with a 7-day grace period; see GET /v1/teams/{slug}/removals.
Response: 204.
Errors:
403—your role does not allow that.404—not a member;no such team.409—transfer ownership before leaving, for the last owner leaving;a team must keep at least one owner, for removing the last owner.
POST /v1/teams/{slug}/members/{email}/pause
Pauses a member. A paused member keeps their row and role but cannot act under the team. /v1, git, and the registry refuse them. Their desks stop, and their team workspaces stop.
Nothing is deleted. Pause is lifted with unpause. Takes no body and a desk.
You need the reach to pause that member. An admin can pause members and admins. An owner can pause anyone. A superadmin can pause any member of any team. Nobody can pause or unpause themselves. Git and registry access follows within a minute.
Response: 204, also when the member was already paused.
Errors:
403—you cannot pause yourself;your role does not allow that.404—no such team;not a member.409—a team must keep at least one active owner.
POST /v1/teams/{slug}/members/{email}/unpause
Lifts a pause. The same reach, the same rules, and the same response as pause; the self refusal reads you cannot unpause yourself. It does not start anything that was stopped. Takes no body and a desk.
POST /v1/teams/{slug}/invites
Invites someone by email. Admins and owners; only an owner can invite an owner. Takes a desk.
| Field | Type | Required | Rule |
|---|---|---|---|
email | string | yes | Must contain @. Lower-cased |
role | string | no | member (the default), admin, or owner |
{ "email": "[email protected]", "role": "member" }Response: 201. The raw token is shown once; the platform keeps only its hash, and id is that hash. Put the token in the link you send. An invitation lasts 7 days.
{ "id": "5d41402abc4b2a76b9719d911017c592a1b2c3d4e5f60718293a4b5c6d7e8f90", "token": "9f2c…64 hex characters…e1", "email": "[email protected]", "role": "member", "team_name": "Acme" }Errors:
400—a valid email is required.403—your role does not allow that;only an owner can invite an owner.404—no such team.409—already a member.
DELETE /v1/teams/{slug}/invites/{id}
Revokes an open invitation. {id} is the id from the team document or from the invite response. Admins and owners. Takes a desk and no body.
Response: 204.
Errors:
403—your role does not allow that.404—no such invitation;no such team.
GET /v1/invites/{token}
What an invitation is for, so a page can show it before the person agrees. Takes a desk. {token} is the raw token. A token that is spent, expired, revoked, or invented is 404, all alike.
Response: 200.
{ "team": "acme", "teamName": "Acme", "email": "[email protected]", "role": "member", "invitedBy": "[email protected]" }Errors:
401— no desk (auth required).404—no such invitation.
POST /v1/invites/{token}/accept
Joins the team the invitation names. Takes a desk, and the desk must be for the address the invitation was sent to. Takes no body.
Response: 200.
{ "team": "acme" }Errors:
403—this invitation was sent to a different email address.404—no such invitation.409—sign in first, when the address has never signed in.
GET /v1/teams/{slug}/removals
Who was removed from the team and is waiting to be cleaned up. Their team workspaces and desks are marked when they leave and deleted after a 7-day grace period; re-adding the person clears the marks. You must be an admin or owner of the team, or a superadmin. This route is served by the workspace tier, so it takes a desk or a CLI login and its 401 is missing or invalid token.
Response: 200, an array of the people waiting. delete_at is when their things become eligible for deletion, RFC 3339.
[{ "owner": "asha", "delete_at": "2026-10-25T08:00:00Z" }]owner is the person's handle. Deletion also needs the region to have member-removal deletion switched on; otherwise the marks stand and nothing is deleted.
Errors:
403—only a team admin can do this.404— you are not in the team (empty body).
POST /v1/teams/{slug}/members/{email}/delete-now
Ends the grace period for one removed person: their team workspaces and desks become eligible for deletion at once. Where member-removal deletion is on, the platform deletes them within a minute. It never deletes a snapshot, a volume, a repository, an image, or a team environment. You must be an admin or owner of the team, or a superadmin. Like the removals list it is served by the workspace tier: a desk or a CLI login, and 401 missing or invalid token.
{email} is the person's handle, not an email, here; an email in that segment is refused with 400. You must confirm by naming both:
| Field | Type | Required | Rule |
|---|---|---|---|
person | string | yes | The handle in the path, any case |
team | string | yes | The slug in the path, any case |
{ "person": "asha", "team": "acme" }Response: 202. deletes_enabled says whether the region will act on the mark now.
{ "deletes_enabled": true }Errors:
400—confirm by naming the person and the team exactly;name the person by their handle; {handle} is not one.403—only a team admin can do this.404— you are not in the team (empty body).409—still a member of the team;not pending removal.503—team membership could not be checked.
Keys
A key belongs to a person, never to a team or a workspace. Registering one admits it to every team where you are an active member. Removing a member stops admitting the keys of that member.
Keys come in two kinds. An SSH key is for git over SSH and for connecting to your workspaces. A signing key (SSH or GPG) proves that you wrote a commit and never opens a connection. You can add the same key as both kinds; they are two entries.
All the key routes take a desk or a CLI login.
POST /v1/keys
Adds a key.
| Field | Type | Required | Rule |
|---|---|---|---|
key | string | yes | An OpenSSH public key line, or an armoured OpenPGP public key block |
name | string | no | Default: the key's comment, else ssh key; for a GPG key the first verified email on it, else GPG key |
signing | boolean | no | true registers it for signing only. A GPG key must be true |
A body that carries a non-empty owner is 400: a key is yours, so there is no owner to name.
{ "name": "laptop", "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI… karthik@laptop", "signing": false }Response: 201, the entry. _id is the key's SHA-256 fingerprint, prefixed sign: for a signing entry. owner is your email. fingerprints lists the fingerprints the entry answers to, lower-cased; a GPG key lists its primary key and every subkey. material is the key you sent, normalized to three fields for SSH.
{ "_id": "SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8", "kind": "sshkey", "owner": "[email protected]", "createdBy": "[email protected]", "name": "laptop", "material": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI… karthik@laptop", "fingerprints": ["sha256:nthbg6kxupjwgl7e1igocsprotmxcdarlvikw6e5sy8"], "createdAt": { "$date": { "$numberLong": "1760400000000" } }}Errors:
400—keys belong to you, not to a namespace;a GPG key can only be added as a signing key;that is not an OpenPGP public key; or the SSH key line cannot be parsed.409—that key is already added.
GET /v1/keys
Lists your keys. ?kind=signing lists signing keys; anything else, or no query, lists SSH keys.
Response: 200, an array of the entries shown above. An entry added before keys were kept has no material; it still clones over SSH but cannot reach a workspace, so add it again.
DELETE /v1/keys/{*id}
Revokes a key. {*id} is the entry's _id. A fingerprint contains /, so send it as it is or percent-encoded; both reach the same route. Only you can revoke your own key; a teammate's is 404. Takes no body.
Response: 204.
Errors:
404—no such credential.
Tokens
An API token opens a fixed set of /v1 routes for a program, with no browser login. You choose its reach (owner), what it can do (access or grants), and its expiry when you make it. A token acts as you inside those limits and never as more than you. The guide is API tokens.
The three token routes take a desk or a CLI login. An API token on any of them is 401: a token cannot make, list, or revoke tokens.
POST /v1/tokens
Makes a token.
| Field | Type | Required | Rule |
|---|---|---|---|
name | string | yes | What it is for. 1 to 60 characters, trimmed |
owner | string | no | Your handle (the default), a team where you are an active member, or * for your handle and every team that you belong to |
access | string[] | one of access, grants | One or more of monitoring, management, executions, destroy, exec. A part gives one grant on every kind in reach, with the verbs of that part that the kind admits, except delete and exec. destroy adds delete and exec adds exec, on the kinds that admit them |
grants | array | one of access, grants | 1 to 20 grants, each { "kind", "verbs", "resources", "own", "limits" } |
max_live_runs | integer | no | 1 to 50. Default: 5. The most live capsules plus pipeline runs that the token has started at one time. Needs a grant with run or exec |
expires_in_days | integer | no | 1 to 365. Default: 90 |
A grant takes a kind, 1 or more different verbs that the kind admits, and 1 to 50 different resources. A resource is *, an exact id or name, or a prefix that ends in *. A selector has at most 63 characters from letters, digits, -, _, ., /, and the final *. For pipeline-runs, a selector matches the name of the pipeline of the run. quota and regions take only read, and their resources are stored as *.
kind | verbs it admits |
|---|---|
workspaces, environments | read, create, update, lifecycle, snapshot, delete |
desks | read, create, update, delete |
capsules | read, logs, run, lifecycle, exec, delete |
pipelines | read, create, update, delete |
pipeline-runs | read, logs, run |
volumes | read, snapshot, delete |
snapshots | delete |
quota, regions | read |
own is a boolean. When true, the grant covers only objects that this token created. It is refused on volumes, snapshots, quota, and regions, which a token never creates.
limits is an object that narrows what a capsule create can ask for. It is accepted only on a capsules grant that holds run or exec.
limits field | Type | Rule |
|---|---|---|
images | string[] | 1 to 50 image selectors: *, an exact reference, or a prefix that ends in *. When limits is set and images is not, only the platform default image passes |
max_timeout_s | integer | 1 to 86400. The longest timeout_secs that a create can ask for. Default: 3600 |
network | boolean | false refuses a create that asks for network. Default: true |
A grant with no limits accepts any image, a timeout up to 3600 seconds, and network.
{ "name": "vendor-nightly", "owner": "acme", "expires_in_days": 30, "access": ["monitoring"]}Response: 201. The token is readable only here; only its digest is kept. _id is that digest. reach is all teams when owner is *, else the owner. maxLiveRuns is null for a token with no run or exec verb.
The grants are what access expanded to. The times are RFC 3339 strings.
{ "token": "klt_0000000000000000000000000000000000000000000000000000000000000000", "_id": "9b74c9897bac770ffc029102a200c5de0a6e9d9a2e0d8c1d5a2f3b4c5d6e7f80", "kind": "apitoken", "owner": "acme", "reach": "acme", "createdBy": "[email protected]", "name": "vendor-nightly", "grants": [ { "kind": "pipelines", "verbs": ["read"], "resources": ["*"] }, { "kind": "pipeline-runs", "verbs": ["read", "logs"], "resources": ["*"] } ], "maxLiveRuns": null, "createdAt": "2026-10-26T06:12:00Z", "expiresAt": "2026-11-25T06:12:00Z", "lastUsedAt": null}The error bodies are plain text. A 422 begins with the field, for example grants[0].verbs: run is not a verb of pipelines (it admits read, create, update, delete).
Errors:
400—give the token a name;that name is too long.404—no such owner, for a team that you are not an active member of.409—pick a username before making a token;tokens: {n} of 25 in use for {owner}; revoke one to make another.422— theaccesssentences:access: give access or grants, not both;access: give access (monitoring, management, executions, destroy, exec) or grants;access: give at least one of monitoring, management, executions, destroy, exec;access: … is listed twice;access: … is not monitoring, management, executions, destroy or exec;access: that selects no verb of any kind.422— the token sentences:expires_in_days: must be 1 to 365;max_live_runs: must be 1 to 50;max_live_runs: belongs to executions; give a grant with the run or exec verb.422— the grant sentences:grants: give at least one grant;grants: at most 20 grants;grants[i].kind: … is not a kind a token can hold;grants[i].verbs: give at least one verb;grants[i].verbs: … is not one of …;grants[i].verbs: … is not a verb of {kind} (it admits …);grants[i].verbs: … is listed twice;grants[i].own: a token never creates {kind}, so own means nothing for it;grants[i].resources: give "*" or at least one selector;grants[i].resources: at most 50 selectors;grants[i].resources: an empty selector;grants[i].resources: … is not a name, an id or a prefix ending in *;grants[i].resources: … is listed twice;grants[i].resources: {kind} takes no selector;grants[i]: the same grant twice.422— the limit sentences:grants[i].limits: only a capsules grant takes limits;grants[i].limits: limits belong to executions; give the run or exec verb;grants[i].limits.images: give 1 to 50 image selectors, or leave it out;grants[i].limits.images: … is not an image reference or a prefix ending in *;grants[i].limits.images: … is listed twice;grants[i].limits.max_timeout_s: must be 1 to 86400.502—could not create the token.
GET /v1/tokens
Lists the tokens that you made, newest first. Another person's tokens are never listed, not even a teammate's token for the same team. ?owner= narrows the list to one reach, for example a team handle or *.
Response: 200, an array of the entries shown above without token.
[{ "_id": "9b74c989…", "kind": "apitoken", "owner": "*", "reach": "all teams", "createdBy": "[email protected]", "name": "ci", "grants": [{ "kind": "workspaces", "verbs": ["read"], "resources": ["*"] }], "maxLiveRuns": null, "createdAt": "2026-10-18T06:12:00Z", "expiresAt": "2027-01-16T06:12:00Z", "lastUsedAt": null }]lastUsedAt is null until the first use. The platform records a use at most once an hour.
Errors:
502—could not list tokens.
DELETE /v1/tokens/{id}
Revokes a token at once. {id} is the _id. Only the creator can revoke a token. Takes no body.
Response: 204.
Errors:
404—no such credential, for an unknown id, a key's id, or a token that another person made.502—could not revoke.
Using a token
Send Authorization: Bearer klt_…, or set KODE_TOKEN. The token reaches the workspace, environment, desk, capsule, pipeline, pipeline run, volume, and snapshot routes, and reads quota and regions, within its grants. Each route needs one verb of one kind.
Starting a capsule or a pipeline run needs run. Reading a log needs logs. Attaching to a capsule needs exec. A route that references a second resource, for example a clone from a snapshot, needs a grant for that resource too.
Every other route is 401 missing or invalid token, the answer for a wrong, expired, or revoked token. These include the routes for tokens, keys, teams, invitations, secrets, connectors, requests, administration, sign-in, git credentials, and tool servers. Three more requests are the same 401: a body that holds ${{ secrets., a pipeline definition that has secrets, and a capsule create outside the limits of its grant. A one-team token that asks for another team (?owner= or team) gets 403 this token acts only for {team}. A one-team token stops when its creator leaves the team. Removing a member also revokes the tokens that this member made for the team.
A token that already has max_live_runs live capsules and pipeline runs gets 429 with this token has {n} runs in flight; the limit is {max} on the next start. Another run can start when one ends.
A list route answers only the items that the grants select. A page can be short or empty while the x-next-cursor header names more.
CLI login
kode login signs a computer in without a password. It asks for a code, you approve the code in the console while signed in, and the CLI collects a token. The token is a CLI login. It lasts 30 days and carries your username. You can revoke it at once with DELETE /v1/cli/tokens/{id}.
The three code routes share one limit per client address, by default 20 requests in 10 minutes, and answer 429 with Retry-After past it.
POST /v1/cli/code
Starts a login. Anonymous; it grants nothing until a signed-in person approves the code.
| Field | Type | Required | Rule |
|---|---|---|---|
device | string | no | What to call this computer. The approver sees it. At most 60 characters; default a computer |
deviceId | string | no | An id the CLI keeps for this computer: 16 to 64 hex digits. An approved login retires every older login of that computer. Anything else is ignored |
Response: 201. expiresIn is in seconds: the code is worth typing for 10 minutes. code has no vowels and no 0, O, 1, or I.
{ "code": "BCDF-GH23", "poll": "5f2d8a0b9c3e4d1f7a6b5c4d3e2f1a0b", "expiresIn": 600 }Keep poll private; it is what collects the token.
Errors:
429— too many codes from this address.
GET /v1/cli/code/{code}
Which computer is asking, so the approval page can show it before it offers a button. Takes a desk only; a CLI login cannot approve another. The code is case-insensitive.
Response: 200.
{ "device": "karthik's laptop", "expiresAt": "2026-10-18T06:22:00Z" }Errors:
404—no such code, for an unknown, expired, or already approved code alike.429— as above.
POST /v1/cli/approve
Approves a code as you. Takes a desk only, and you must have chosen a username.
| Field | Type | Required | Rule |
|---|---|---|---|
code | string | yes | The code, in any case |
{ "code": "bcdf-gh23" }Response: 204.
Errors:
400—pick a handle before signing in from the CLI.404—no such code.409—that code was already used.429— as above.
GET /v1/cli/token
The CLI polls this with ?poll= from POST /v1/cli/code. It is anonymous. The token is handed over once: a second poll is 410.
Response: 202 with an empty body while nobody has approved; then 200 once.
{ "token": "eyJhbGciOiJIUzI1NiJ9…", "expiresAt": "2026-11-17T06:12:00Z" }Errors:
410—that login expired: unknown, expired, nopoll, or already collected.
GET /v1/cli/tokens
Lists CLI logins. Takes a desk or a CLI login. The query ?owner= defaults to your own handle.
Response: 200.
[{ "id": "8c1f0e7a6b5d4c3b2a190807f6e5d4c3", "name": "karthik's laptop", "createdAt": "2026-10-18T06:12:00Z", "expiresAt": "2026-11-17T06:12:00Z", "lastUsedAt": "2026-10-20T09:00:00Z" }]lastUsedAt is the last request the login authenticated, to the hour, or null if it has not been used. expiresAt is createdAt plus 30 days. Past that the login stops working on its own.
Errors:
400—owner is required, when your token carries no username.404—no such owner.
DELETE /v1/cli/tokens/{id}
Signs a computer out. The CLI login stops working at once, on every route. {id} is the id from the list. Takes a desk or a CLI login and no body.
Response: 204.
Errors:
404—no such credential.