Skip to content
Orbital

HTTP API

Find the running server, authenticate, then list runs and projects and start runs from a script

Call Orbital’s HTTP API when a script needs to list runs and projects or start a run. The running server answers under /api, on the same port as the browser app:

list-runs.sh
record=~/.orbital/server.json
curl --silent --show-error \
--header "Authorization: Bearer $(jq -r '.secret // ""' "$record")" \
"$(jq -r .url "$record")api/runs" \
|| echo "No Orbital server is running." >&2

For a coding agent, the MCP endpoint is usually the better choice. Its tools describe themselves and return what an agent needs.

A running server records where it listens in ~/.orbital/server.json. This is true of orbital serve and of the server the desktop app or orbital app owns.

Field Holds
url The server’s address.
secret Only for a server a window owns. Every request to that server must carry it.

Only you can read the file. The server removes it when it stops. A record whose URL does not answer is out of date; start Orbital again to replace it.

Server What a request needs
orbital serve No secret. A request without an Authorization header is fine.
A window’s server, from the desktop app or orbital app Authorization: Bearer <secret>, with the secret from ~/.orbital/server.json. Without it, the server answers 401.

The examples read the address and the secret with jq. .secret // "" gives an empty secret for orbital serve, which it accepts.

The MCP endpoint at /mcp is different. It always needs its own bearer token, even on orbital serve. See the endpoint.

Every server also applies these rules:

  • A server bound to a loopback address answers only to the host names localhost, 127.0.0.1 and [::1]. Other host names get 403.
  • A server bound to a network address with --bind answers to any host name. Protecting that network is up to you.
  • A change request that another web site sends gets 403.

Lists every run the server knows.

GET /api/runs

It takes no parameters.

It returns an array of runs. Each run has its runId, workflowName, goal, project and phase, among other fields. The phase field explains phase.

Example, with the response trimmed to a few fields:

Terminal window
curl --header "Authorization: Bearer $secret" "${url}api/runs"
[
{
"runId": "2026-09-24-npn6fk",
"workflowName": "implement-ticket",
"goal": "https://linear.app/acme/issue/IE-3770",
"project": "proj_r3tQMqBPBB5p",
"phase": "Executing"
}
]

Related: Runs, Watch a run.

Reads one run, in the same shape as an item of List runs.

GET /api/runs/{runId}
Name Type Required Default Description
runId string, in the path Required The run id.

It returns the run. A run the server does not know answers 404.

Example:

Terminal window
curl --header "Authorization: Bearer $secret" "${url}api/runs/2026-09-24-npn6fk"
{
"runId": "2026-09-24-npn6fk",
"workflowName": "implement-ticket",
"phase": "Ended",
"outcome": "success"
}

Related: Watch a run.

Starts a run in a project, as orbital run start does.

POST /api/runs

The body is a JSON object:

Name Type Required Default Description
project string Required The project id. List projects gives it.
goal string Required, unless you give ticket The run’s goal.
workflow string Optional The project’s default workflow The workflow name.
inputs object of strings Optional The workflow’s inputs, such as {"ticket_url": "<ticket URL>"}. The shipped ticket and epic workflows take ticket_url.
title string Optional A generated title One line of up to 80 characters that the run is listed under.
harness string Optional The workflow’s choice claude, codex or opencode.
model string Optional A model for that harness. Only with harness.
start boolean Optional true false records the run without starting it.
ticket string Optional Older; use goal and inputs instead. See below.

ticket takes a ticket’s URL or identifier. It becomes the goal when goal is missing, and it is passed as a workflow input named ticket. A workflow that declares no ticket input refuses it with a 400 that says the input ticket is not declared by the workflow. It is not the same as ticket_url, which is a workflow input you send inside inputs.

Status Meaning
200 The run was created. The body is {"runId": "..."}.
400 The server cannot take the request. The body’s detail names the problem, such as a missing input or an unknown workflow.
401 The secret is missing or wrong.
404 The project does not exist.

The run queues like any other run. It starts when a slot is free, under the cap in Settings > General.

Example, starting a run from a ticket URL:

start-run.sh
record=~/.orbital/server.json
project=proj_r3tQMqBPBB5p
ticket=https://linear.app/acme/issue/IE-3770
curl --silent --show-error --fail-with-body \
--header "Authorization: Bearer $(jq -r '.secret // ""' "$record")" \
--header "Content-Type: application/json" \
--data "$(jq -n --arg project "$project" --arg ticket "$ticket" \
'{project: $project, workflow: "implement-ticket", goal: $ticket, inputs: {ticket_url: $ticket},
title: "Pay invoices from the portal"}')" \
"$(jq -r .url "$record")api/runs"
{ "runId": "2026-09-24-npn6fk" }

Related: Start a run, The run queue.

Lists the projects the server keeps. A project’s id is what Start a run takes as project.

GET /api/projects

It takes no parameters.

It returns an array of projects. Each has an id, a name and its folders, each with a name and a path.

Example, printing each project’s id and name:

list-projects.sh
record=~/.orbital/server.json
curl --silent --show-error \
--header "Authorization: Bearer $(jq -r '.secret // ""' "$record")" \
"$(jq -r .url "$record")api/projects" | jq -r '.[] | "\(.id) \(.name)"'
proj_r3tQMqBPBB5p acme-portal

Related: Projects and folders, Create a project.

Reports the server’s version, whether a newer release is available, and how many projects and runs it holds.

GET /api/health

It takes no parameters.

It returns version, available, projects and runs.

Example:

Terminal window
curl --header "Authorization: Bearer $secret" "${url}api/health"
{ "projects": 3, "runs": 41, "version": "0.3.0", "available": { "_tag": "UpToDate", "running": "0.3.0" } }

Related: Install Orbital, Troubleshooting.

The browser app uses the same API. These are the other groups a script is most likely to need. All paths start with /api.

Group Main routes What it covers
Runs /api/runs/{runId} and below Controls, messages and exports runs, and reads their thread, history and logs.
Workflows /api/workflows?project=<id>, /api/workflows/{name} and below Lists the workflows a project offers, and reads, validates and edits workflows.
Projects /api/projects/{projectId} and below Creates and changes projects and their folders.
Settings /api/settings, /api/settings/changes, /api/settings/mcp Reads and changes settings, lists the recent changes, and reads the MCP connection. A change request may name its sender with the orbital-setting-source header; one that names none is recorded as a request that did not say who sent it.
Skills /api/skills, /api/skills/install Lists the bundled skills and installs them.
Harnesses /api/harnesses, /api/harnesses/check Reads and checks whether each harness is ready.

The API has other groups the browser app uses, such as chats, triggers and personas.

  • MCP tools: the better choice for a coding agent, with self-describing tools
  • Command line: start a run and change settings from a terminal
  • Runs: what a run’s phase means
  • Start a run: every way to start a run