opsdocsConsole

Ops platform / HTTP API

Login and the CLI

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.

Updated 13 Oct 2026View as Markdown

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.

POST /api/v1/auth/device

Start a device grant.

Body: DeviceAuthorizeBody

StatusMeansBody
200DoneDeviceAuthorizeResponse
422client must be cli or mcp
429Too many grants started from this address

POST /api/v1/auth/device/token

Exchange a device_code for the token the approver authorized.

Body: DevicePollBody

StatusMeansBody
200DoneDeviceTokenResponse
400authorization_pending, slow_down, expired_token, access_denied or unsupported_grant_typeDeviceErrorResponse

GET /api/v1/auth/device/{user_code}

What is being approved, plus every role the approver could approve it with.

ParameterInTypeAbout
user_codepathstring
StatusMeansBody
200DoneDeviceGrantResponse
401No token, or a token that is not valid
404Not found
429Too many code lookups from this address

POST /api/v1/auth/device/{user_code}/approve

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.

ParameterInTypeAbout
user_codepathstring

Body: DeviceApproveBody

StatusMeansBody
204Done, with no body
401No token, or a token that is not valid
403A machine operator cannot approve a device
404Not found
409Already decided
422A token cannot carry more than the approver does

POST /api/v1/auth/device/{user_code}/deny

Deny the device. No body: there is nothing to decide but no.

ParameterInTypeAbout
user_codepathstring
StatusMeansBody
204Done, with no body
401No token, or a token that is not valid
404Not found
409Already decided

POST /api/v1/cli/install-codes

Mint a single use install code for the caller, in one project.

Body: InstallCodeBody

StatusMeansBody
200DoneInstallCodeResponse
401No token, or a token that is not valid
403A machine, or an operator with no role here
404No such project
422A token cannot carry more than the caller does

GET /api/v1/cli/install.sh

Redeem an install code and answer the installer for this machine.

StatusMeansBody
200The installer, holding a freshly minted tokentext/x-shellscript bytes
401No code, or one that is used, expired or unknown
503Nothing is published to install yet

GET /api/v1/cli/release

The newest CLI release for one platform, which ops update compares itself with.

ParameterInTypeAbout
targetquerystring${platform}-${arch}, node's spelling
StatusMeansBody
200DoneCliReleaseResponse
401No token, or a token that is not valid
403Your role does not allow this
404Nothing published for that target yet
422Not a target ops publishes for

GET /api/v1/cli/releases/{version}/{target}/download

The CLI binary of one release for one platform.

ParameterInTypeAbout
versionpathstring
targetpathstring
StatusMeansBody
200The binary
401No token, or a token that is not valid
403Your role does not allow this
404No such release
422Not a target ops publishes for

Types

DeviceAuthorizeBody

POST /api/v1/auth/device, the request body. Everything is optional: a bare {} is a valid device authorization request.

FieldTypeAbout
client (optional)string or nullcli (default) or mcp.
client_name (optional)string or nullWhat the client calls itself, shown to the approver. Never trusted, and truncated to 80 characters.
project (optional)string or nullThe project the client would like the token pinned to. The approver decides.

DeviceAuthorizeResponse

FieldTypeAbout
device_codestring
expires_ininteger
intervalinteger
user_codestringDisplay form, XXXX-XXXX.
verification_uristring
verification_uri_completestring

DevicePollBody

POST /api/v1/auth/device/token, the poll body.

FieldTypeAbout
device_codestring
grant_type (optional)string or null

DeviceTokenResponse

The poll's success body. scope is space separated, the OAuth convention, and holds the token's snapshotted permission keys.

FieldTypeAbout
access_tokenstring
expires_in (optional)integer or nullSeconds until the token expires; null when the approver chose no expiry.
operatorstringWho approved it, and therefore who it acts as.
project (optional)string or nullThe slug the token is pinned to, null when it acts wherever its operator may.
rolestring
scopestring
token_typestringAlways Bearer.

DeviceErrorResponse

One field, and the five values are the whole vocabulary this route answers with.

FieldTypeAbout
errorstring
error_descriptionstringA whole sentence for a human, which the CLI prints under the code.

DeviceGrantResponse

GET /api/v1/auth/device/{user_code}, what the approval page renders.

FieldTypeAbout
clientstringcli | mcp
client_namestring"" when the client named nothing.
created_atstring
default_project (optional)string or null
default_role_id (optional)string (uuid) or null
expires_atstring
ipstring
requested_project (optional)string or null
rolesarray of DeviceRoleOptionThe page preselects default_role_id.
statusstringpending | approved | denied | expired
user_codestringDisplay form.

DeviceApproveBody

POST /api/v1/auth/device/{user_code}/approve

FieldTypeAbout
expires_in_days (optional)integer or nullDays until expiry. Omitted = OPS_TOKEN_TTL_DAYS (90), 0 = no expiry.
name (optional)string or nullShown on the operator's Tokens list. Omitted = "ops CLI on {client_name}".
project (optional)string or nullnull = the token acts wherever the approver may; a slug pins it.
role_idstring (uuid)The role the minted token snapshots. Required: there is no default role anywhere in this codebase and there is not one here either.

InstallCodeBody

FieldTypeAbout
projectstringThe project whose Develop page the line was copied on.

InstallCodeResponse

FieldTypeAbout
codestringopsi_…, for the install line's Authorization header. Shown once and never stored: only its digest is.
expires_inintegerSeconds until the code stops working.
rolestringThe role the machine's token will carry, for the line under the snippet.
token_project (optional)string or null

CliReleaseResponse

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.

FieldTypeAbout
digeststringLowercase hex SHA-256 of the binary. The CLI verifies the download against this BEFORE it replaces its own executable.
downloadstringThe path to fetch, /api/v1/cli/releases/{version}/{target}/download.
published_atstringWhich release is current is decided by this, never by the number.
size_bytesinteger
targetstring${platform}-${arch}, echoed back so a client can see it was understood.
versionstringThe version the CLI will report for itself once it has updated.

DeviceRoleOption

FieldTypeAbout
idstring (uuid)
mintablebooleanAdvisory: approve runs the real intersection again.
namestring
project (optional)string or null