> For the index of every page in Orbital's docs, read https://docs.beta.runorbital.dev/llms.txt.

# 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:

```sh title="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](/reference/mcp-tools/) is usually the better choice. Its tools describe themselves and return what an agent needs.

## Find the server

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.

## Authentication

| 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](/reference/mcp-tools/#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`.

## Runs

### List runs

Lists every run the server knows.

```text
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](/runs/#the-phase-field) explains `phase`.

Example, with the response trimmed to a few fields:

```sh
curl --header "Authorization: Bearer $secret" "${url}api/runs"
```

```json
[
  {
    "runId": "2026-09-24-npn6fk",
    "workflowName": "implement-ticket",
    "goal": "https://linear.app/acme/issue/IE-3770",
    "project": "proj_r3tQMqBPBB5p",
    "phase": "Executing"
  }
]
```

Related: [Runs](/runs/), [Watch a run](/runs/watch-a-run/).

### Get a run

Reads one run, in the same shape as an item of [List runs](#list-runs).

```text
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:

```sh
curl --header "Authorization: Bearer $secret" "${url}api/runs/2026-09-24-npn6fk"
```

```json
{
  "runId": "2026-09-24-npn6fk",
  "workflowName": "implement-ticket",
  "phase": "Ended",
  "outcome": "success"
}
```

Related: [Watch a run](/runs/watch-a-run/).

### Start a run

Starts a run in a project, as [`orbital run start`](/reference/command/#orbital-run-start) does.

```text
POST /api/runs
```

The body is a JSON object:

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `project` | string | Required | | The project id. [List projects](#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:

```sh title="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"
```

```json
{ "runId": "2026-09-24-npn6fk" }
```

Related: [Start a run](/runs/start-a-run/), [The run queue](/runs/queue/).

## Projects

### List projects

Lists the projects the server keeps. A project's `id` is what [Start a run](#start-a-run) takes as `project`.

```text
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:

```sh title="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)"'
```

```text
proj_r3tQMqBPBB5p  acme-portal
```

Related: [Projects and folders](/projects/), [Create a project](/projects/create-a-project/).

## Health

### Check the server

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

```text
GET /api/health
```

It takes no parameters.

It returns `version`, `available`, `projects` and `runs`.

Example:

```sh
curl --header "Authorization: Bearer $secret" "${url}api/health"
```

```json
{ "projects": 3, "runs": 41, "version": "0.3.0", "available": { "_tag": "UpToDate", "running": "0.3.0" } }
```

Related: [Install Orbital](/get-started/install/), [Troubleshooting](/troubleshooting/).

## Other routes

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.

## Related

- [MCP tools](/reference/mcp-tools/): the better choice for a coding agent, with self-describing tools
- [Command line](/reference/command/): start a run and change settings from a terminal
- [Runs](/runs/#the-phase-field): what a run's `phase` means
- [Start a run](/runs/start-a-run/): every way to start a run
