API tokens and the HTTP API

A password belongs to a person at a browser. Everything else that calls the API uses a personal API token: a build job, a deployment script, another service.

Issue a token

Open the profile screen of the account and add a token. Give it a name, the scopes that it needs, and the number of days until it expires. Zero days means that it does not expire on its own.

Midair shows the token once, at the moment that it makes it, and it keeps only a hash. Nothing can show the token again. Copy it before you leave the screen.

midair token issue makes a token from the command line. See Command-line client.

A token looks like this:

mid_<id>_<secret>

Send it in the Authorization header:

Authorization: Bearer mid_<id>_<secret>

Scopes

A token is issued for named scopes, and never for everything. A token in a CI variable costs less to lose than the password that it stands in for, and that holds only while it can do less.

Scope

What it opens

read

Read the runs, the analyses, the comments and the log pages behind them.

runs

Start test runs and upload logs.

analysis

Start and stop analyses.

comments

Write comments on results.

devices

Start and stop monitored devices, and send them commands.

distributions

Upload builds to the distribution registries.

mcp

Let an AI assistant use this account through the MCP endpoint. See MCP endpoint.

A scope is a ceiling and not a grant. A call with a token acts as the owner of the token, and it is subject to the same team seats as the screens. See Users, teams and access.

The token list itself is out of reach of every token. Use a password to issue, list or revoke a token, so that the loss of one token cannot be turned into keeping one.

The collections

The item API of the core reads and writes the collections. A token with the read scope reads these:

test, analysis, test_suite, workspace, runtime, comment, team, notification, node, distribution, distribution_test, distribution_runtime

A token writes a collection with the scope that the collection asks for:

Collection

Scope to write

test

runs

analysis

analysis

runtime

devices

distribution

distributions

distribution_test

distributions

distribution_runtime

distributions

The routes

These routes belong to Midair itself. Each one names the scope that it needs.

Route

Scope

GET /test/analysis

read

GET /test/ai_review

read

GET /test/query_log

read

GET /analysis/tree

read

GET /analysis/file

read

GET /comment/list

read

GET /comment/people

read

GET /comment/refs

read

GET /comment/today

read

POST /test/run

runs

POST /test/upload_log

runs

POST /test/analyze_log

runs

POST /analysis/run

analysis

POST /analysis/start

analysis

POST /analysis/stop

analysis

POST /comment/add

comments

POST /runtime/start

devices

POST /runtime/stop

devices

POST /delta/command

devices

POST /distribution/upload

distributions

POST /mcp

mcp

The full description

The server describes its own HTTP surface. Two addresses serve it:

GET /openapi.json

An OpenAPI 3.1 document for this deployment, with the plugin routes in it.

GET /docs

The same document as a static page. It uses no scripts and no external files, so it works offline.

The server generates the document rather than shipping it, because half of the surface exists only at run time. Both addresses are public. A description is not a credential, and every endpoint in it still authenticates its own callers. An instance that prefers not to publish the list starts the core with --openapi-private, which serves both to administrators only.