How the CLI and an MCP client get a token (a device code that someone with a console login approves), and the CLI releases it updates itself from.
How the CLI and an MCP client get a token (a device code that someone with a console login approves), and the CLI releases it updates itself from.
Start a device grant.
Body: DeviceAuthorizeBody
| Status | Means | Body |
|---|
| 200 | Done | DeviceAuthorizeResponse |
| 422 | client must be cli or mcp | |
| 429 | Too many grants started from this address | |
Exchange a device_code for the token the approver authorized.
Body: DevicePollBody
What is being approved, plus every role the approver could approve it with.
| Parameter | In | Type | About |
|---|
user_code | path | string | |
| Status | Means | Body |
|---|
| 200 | Done | DeviceGrantResponse |
| 401 | No token, or a token that is not valid | |
| 404 | Not found | |
| 429 | Too many code lookups from this address | |
Approve the device, with the role, the pin and the expiry the token will carry. 204: the secret goes to the polling client and to nobody else, the approver included.
| Parameter | In | Type | About |
|---|
user_code | path | string | |
Body: DeviceApproveBody
| Status | Means | Body |
|---|
| 204 | Done, with no body | |
| 401 | No token, or a token that is not valid | |
| 403 | A machine operator cannot approve a device | |
| 404 | Not found | |
| 409 | Already decided | |
| 422 | A token cannot carry more than the approver does | |
Deny the device. No body: there is nothing to decide but no.
| Parameter | In | Type | About |
|---|
user_code | path | string | |
| Status | Means | Body |
|---|
| 204 | Done, with no body | |
| 401 | No token, or a token that is not valid | |
| 404 | Not found | |
| 409 | Already decided | |
Mint a single use install code for the caller, in one project.
Body: InstallCodeBody
| Status | Means | Body |
|---|
| 200 | Done | InstallCodeResponse |
| 401 | No token, or a token that is not valid | |
| 403 | A machine, or an operator with no role here | |
| 404 | No such project | |
| 422 | A token cannot carry more than the caller does | |
Redeem an install code and answer the installer for this machine.
| Status | Means | Body |
|---|
| 200 | The installer, holding a freshly minted token | text/x-shellscript bytes |
| 401 | No code, or one that is used, expired or unknown | |
| 503 | Nothing is published to install yet | |
The newest CLI release for one platform, which ops update compares itself with.
| Parameter | In | Type | About |
|---|
target | query | string | ${platform}-${arch}, node's spelling |
| Status | Means | Body |
|---|
| 200 | Done | CliReleaseResponse |
| 401 | No token, or a token that is not valid | |
| 403 | Your role does not allow this | |
| 404 | Nothing published for that target yet | |
| 422 | Not a target ops publishes for | |
The CLI binary of one release for one platform.
| Parameter | In | Type | About |
|---|
version | path | string | |
target | path | string | |
| Status | Means | Body |
|---|
| 200 | The binary | |
| 401 | No token, or a token that is not valid | |
| 403 | Your role does not allow this | |
| 404 | No such release | |
| 422 | Not a target ops publishes for | |
POST /api/v1/auth/device, the request body. Everything is optional: a bare {} is a valid device authorization request.
| Field | Type | About |
|---|
client (optional) | string or null | cli (default) or mcp. |
client_name (optional) | string or null | What the client calls itself, shown to the approver. Never trusted, and truncated to 80 characters. |
project (optional) | string or null | The project the client would like the token pinned to. The approver decides. |
| Field | Type | About |
|---|
device_code | string | |
expires_in | integer | |
interval | integer | |
user_code | string | Display form, XXXX-XXXX. |
verification_uri | string | |
verification_uri_complete | string | |
POST /api/v1/auth/device/token, the poll body.
| Field | Type | About |
|---|
device_code | string | |
grant_type (optional) | string or null | |
The poll's success body. scope is space separated, the OAuth convention, and holds the token's snapshotted permission keys.
| Field | Type | About |
|---|
access_token | string | |
expires_in (optional) | integer or null | Seconds until the token expires; null when the approver chose no expiry. |
operator | string | Who approved it, and therefore who it acts as. |
project (optional) | string or null | The slug the token is pinned to, null when it acts wherever its operator may. |
role | string | |
scope | string | |
token_type | string | Always Bearer. |
One field, and the five values are the whole vocabulary this route answers with.
| Field | Type | About |
|---|
error | string | |
error_description | string | A whole sentence for a human, which the CLI prints under the code. |
GET /api/v1/auth/device/{user_code}, what the approval page renders.
| Field | Type | About |
|---|
client | string | cli | mcp |
client_name | string | "" when the client named nothing. |
created_at | string | |
default_project (optional) | string or null | |
default_role_id (optional) | string (uuid) or null | |
expires_at | string | |
ip | string | |
requested_project (optional) | string or null | |
roles | array of DeviceRoleOption | The page preselects default_role_id. |
status | string | pending | approved | denied | expired |
user_code | string | Display form. |
POST /api/v1/auth/device/{user_code}/approve
| Field | Type | About |
|---|
expires_in_days (optional) | integer or null | Days until expiry. Omitted = OPS_TOKEN_TTL_DAYS (90), 0 = no expiry. |
name (optional) | string or null | Shown on the operator's Tokens list. Omitted = "ops CLI on {client_name}". |
project (optional) | string or null | null = the token acts wherever the approver may; a slug pins it. |
role_id | string (uuid) | The role the minted token snapshots. Required: there is no default role anywhere in this codebase and there is not one here either. |
| Field | Type | About |
|---|
project | string | The project whose Develop page the line was copied on. |
| Field | Type | About |
|---|
code | string | opsi_…, for the install line's Authorization header. Shown once and never stored: only its digest is. |
expires_in | integer | Seconds until the code stops working. |
role | string | The role the machine's token will carry, for the line under the snippet. |
token_project (optional) | string or null | |
The answer to "is the binary running this command still the one ops serves". download is a PATH and not an absolute url, deliberately: the CLI already holds the base url it authenticated against, and a url built from a server side base_url would send a client that reached ops through one name to another one, where its token may not even be presented.
| Field | Type | About |
|---|
digest | string | Lowercase hex SHA-256 of the binary. The CLI verifies the download against this BEFORE it replaces its own executable. |
download | string | The path to fetch, /api/v1/cli/releases/{version}/{target}/download. |
published_at | string | Which release is current is decided by this, never by the number. |
size_bytes | integer | |
target | string | ${platform}-${arch}, echoed back so a client can see it was understood. |
version | string | The version the CLI will report for itself once it has updated. |
| Field | Type | About |
|---|
id | string (uuid) | |
mintable | boolean | Advisory: approve runs the real intersection again. |
name | string | |
project (optional) | string or null | |