# API Reference

Base URL: `https://api.orbcloud.dev`

All endpoints require `Authorization: Bearer orb_YOUR_KEY` unless noted.

---

## Auth

API keys are **self-serve**: register with just an email and the response contains a working `api_key` immediately. No waitlist, no payment, no human approval. Agents can call this endpoint themselves, they do not need a human to provision a key.

### Register

No auth required.

```
POST /api/v1/auth/register
```

```json
{"email": "you@example.com"}
```

Returns `tenant_id` and `api_key`. Use the `api_key` as `Authorization: Bearer <api_key>` on every other endpoint. Save it, the email is the only recovery path.

### Login

No auth required.

```
POST /api/v1/auth/login
```

```json
{"api_key": "YOUR_KEY"}
```

Returns JWT token.

---

## API Keys

### Create Key

```
POST /v1/keys
```

```json
{"name": "production"}
```

### List Keys

```
GET /v1/keys
```

### Revoke Key

```
DELETE /v1/keys/{key_id}
```

---

## Computers

### Create

```
POST /v1/computers
```

```json
{
  "name": "my-agent",
  "runtime_mb": 2048,
  "disk_mb": 4096
}
```

### List

```
GET /v1/computers
```

### Get

```
GET /v1/computers/{id}
```

### Delete

```
DELETE /v1/computers/{id}
```

---

## Config

### Upload

Upload an `orb.toml` file. See [orb.toml Reference](config-reference.md).

```
POST /v1/computers/{id}/config
Content-Type: application/toml
```

### Get

```
GET /v1/computers/{id}/config
```

---

## Build

### Trigger Build

Clones (first build) or pulls latest commits (subsequent builds) from the configured git repo, then runs build steps from orb.toml. Synchronous, blocks until complete. Use a long timeout (e.g., `curl -m 600`). Typically takes 1-3 minutes.

Every build call runs `git fetch --depth=1 && git reset --hard FETCH_HEAD`, so calling `/build` after pushing new code always lands the latest commit. No `?force=true` required.

```
POST /v1/computers/{id}/build
```

---

## Deploy

### Deploy Agent

Starts the agent as defined in orb.toml. The agent runs as a persistent process.

```
POST /v1/computers/{id}/agents
```

The body is optional, `{}` deploys with the stored config and no overrides:

```json
{}
```

All fields are optional:

| Field | Type | Description |
|---|---|---|
| `org_secrets` | map[string]string | Injected into the agent's environment. See below. |
| `orb_config` | object | Inline orb.toml override. If absent, uses the config previously uploaded via `POST /{id}/config`. |

One deploy per computer = one agent. To run N independent worker agents from a single config, use [Swarms](#swarms), each replica gets its own computer and its own `ORB_REPLICA_INDEX`.

#### How env vars and secrets work

Two ways to set env vars in `[agent.env]`:

**Hardcoded**, literal value in `orb.toml`. Always used as-is. `org_secrets` has no effect on these.

```toml
[agent.env]
PORT = "8080"
LOG_LEVEL = "info"
```

**`${VAR}` reference**, value comes from `org_secrets` at deploy time. ORB stores these encrypted per-computer and auto-injects them on every restart, redeploy, or recovery. You never need to supply them again unless rotating to a new value.

```toml
[agent.env]
ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}"
GITHUB_TOKEN = "${GITHUB_TOKEN}"
```

Deploy once with the values:

```json
{
  "org_secrets": {
    "ANTHROPIC_API_KEY": "sk-ant-...",
    "GITHUB_TOKEN": "ghp_..."
  }
}
```

**To rotate a secret**, send a deploy with only the key(s) changing. Other stored secrets are untouched.

```json
{ "org_secrets": { "GITHUB_TOKEN": "ghp_rotated..." } }
```

**To update a hardcoded value**, upload a new `orb.toml` via `POST /{id}/config`, then redeploy.

Returns the spawned agent's `port`, `pid`, and `computer_id`.

### List Agents

```
GET /v1/computers/{id}/agents
```

Returns each agent's `port`, `pid`, `state`, `sandboxed` flag, and `code_version` (short git SHA of the deployed commit, when available).

Agent states:
- **Running**, active in RAM, processing work
- **Frozen**, SIGSTOP'd, waiting for LLM response (seconds)
- **Checkpointed**, serialized to NVMe, zero RAM cost (sleep)
- **Failed**, process exited unexpectedly; call `POST /{id}/restart` to recover

### Sleep (Demote)

Checkpoint an agent to NVMe. Frees its RAM. The agent resumes automatically when its next LLM response arrives, or you can wake it manually.

```
POST /v1/computers/{id}/agents/demote
```

```json
{"port": 10000}
```

### Wake (Promote)

Restore a sleeping agent from NVMe. Full state restored, the agent continues exactly where it left off.

```
POST /v1/computers/{id}/agents/promote
```

```json
{"port": 10000}
```

### Restart Computer

Kills all processes inside the computer (including any orphaned subprocesses), clears the agent registry, and resets the computer to Ready state. The computer's disk, installed packages, cloned source, and persistent data are untouched. Call `POST /{id}/agents` immediately after to redeploy.

Use this to recover a computer stuck in Failed state without deleting it.

```
POST /v1/computers/{id}/restart
```

```json
{
  "computer_id": "abc123",
  "restarted": true,
  "killed_agents": 1
}
```

---

## Swarms

A **swarm** is N independent computers spawned from one config, each computer runs one agent, with its own netns, cgroup, LLM proxy, idle clock, and checkpoint blob. Use this when you want a fleet of identical workers (e.g. 50 shards reviewing GitHub issues, 10 web crawlers running in parallel) without making 100 API calls.

Each replica gets two env vars injected automatically:

- `ORB_REPLICA_INDEX`, `0..N-1`, this replica's slot
- `ORB_REPLICA_COUNT`, `N`, total replicas in the swarm

Your code reads these and self-routes, modulo partition (`item % count == index`), queue claim, range slice, whatever fits the workload. ORB doesn't enforce a coordination pattern.

> **No autoscale.** ORB doesn't auto-scale swarms. Idle replicas already cost zero (sleep), and wake-on-request handles bursts in <1s, so the autoscale problem doesn't exist here. Spawn what you might ever need; over-provisioning is free. If your workload genuinely outgrows N over time, recreate the swarm at the new size, you don't lose work because there's no continuous scaling decision to make.

### Create

```
POST /v1/swarms
```

```json
{
  "name": "clawsweeper",
  "replicas": 50,
  "orb_config": { "agent": { "name": "shard", "lang": "node", "entry": "dist/clawsweeper.js" } },
  "org_secrets": { "OPENAI_API_KEY": "sk-..." },
  "runtime_mb": 1024,
  "disk_mb": 2048
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Unique within your org. Stable identifier; computers are named `{name}-replica-{i}`. |
| `replicas` | int | yes | 1..100. |
| `orb_config` | object | yes | Same shape as a single deploy. The same config is used for every replica. |
| `org_secrets` | map[string]string | optional | Same merge-with-stored semantics as single deploy. |
| `runtime_mb` | int | optional | Per-replica RAM budget. Uniform across the swarm. |
| `disk_mb` | int | optional | Per-replica disk budget. Uniform across the swarm. |

Returns `201 Created` on full success, `207 Multi-Status` if some replicas failed:

```json
{
  "swarm_id": "swm_abc123xyz0",
  "name": "clawsweeper",
  "replicas": 50,
  "deployed": 50,
  "failed": [],
  "computers": [
    { "id": "cmp_xxx", "replica_index": 0, "status": "active", "subdomain": "cmp_xxx.orbcloud.dev" },
    "..."
  ]
}
```

Partial failure example (47/50 deployed, 3 failed during runtime spawn):

```json
{
  "swarm_id": "swm_abc123xyz0",
  "deployed": 47,
  "failed": [
    { "replica_index": 12, "error": "deploy_failed", "message": "..." }
  ],
  "computers": [
    "..."
  ]
}
```

ORB does not auto-rollback on partial failure, you can re-create the swarm under a different name, delete it and retry, or just live with N-k workers if your workload tolerates it.

### List Members

```
GET /v1/swarms/{id}
```

```json
{
  "swarm_id": "swm_abc123xyz0",
  "name": "clawsweeper",
  "replicas": 50,
  "computers": [
    { "id": "cmp_xxx", "replica_index": 0, "status": "active", "subdomain": "cmp_xxx.orbcloud.dev" }
  ]
}
```

### Delete

Atomically tears down all members of the swarm, kills each replica's agent, releases the sandbox, drops checkpoint blobs, removes the DB rows.

```
DELETE /v1/swarms/{id}
```

```json
{
  "swarm_id": "swm_abc123xyz0",
  "deleted": 50,
  "failed": []
}
```

---

## Files

### Read

```
GET /v1/computers/{id}/files
GET /v1/computers/{id}/files/{path}
```

Returns a JSON directory listing for a directory path, or the raw file bytes (with `Content-Type: application/octet-stream`) for a file path. Reads directly from the computer's rootfs on the host, does not wake a sleeping agent. Safe to call repeatedly from a dashboard.

Example, download a file:

```bash
curl -H "Authorization: Bearer $KEY" \
  https://api.orbcloud.dev/v1/computers/$CID/files/agent/code/findings/candidates.json \
  -o candidates.json
```

### Write

```
PUT /v1/computers/{id}/files/{path}
Content-Type: application/octet-stream
Body: raw file bytes
```

Writes the body to `{path}` inside the computer's rootfs. Creates intermediate directories as needed. Overwrites existing files. **Does not wake a sleeping agent**, the file lands on disk; the agent sees it next time it reads. Capped at **200 MB** per request; payloads above return `413 Payload Too Large`.

Path-traversal protected: the final path is canonicalized and must resolve inside the computer's rootfs.

Example, upload a file:

```bash
curl -X PUT -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @./local-file.txt \
  https://api.orbcloud.dev/v1/computers/$CID/files/root/local-file.txt
```

Response:

```json
{ "path": "root/local-file.txt", "size": 42 }
```

Common destinations:

- `/root/…`, scratch space for humans (terminal upload default)
- `/agent/code/…`, source tree (careful: a rebuild will overwrite from git)
- `/agent/data/…`, agent writable data dir, safe across rebuilds

Not for setup, put permanent dependencies in `orb.toml` `[build].steps`, not uploaded by hand. See [Terminal](terminal.md) for the interactive version of the same flow.

---

## Terminal

Browser-based shell into any computer you own. Primarily a **human debug tool**, not how agents interact with their computer.

Open a browser terminal:

```
https://api.orbcloud.dev/terminal/{computer-id}
```

Or connect programmatically via WebSocket:

```
GET /v1/computers/{id}/terminal?key=orb_YOUR_KEY
```

The endpoint upgrades to a WebSocket carrying a real PTY (bash login shell with prompt, line editing, colors, vim/htop/tmux all work). The shell inherits your agent's `[agent.env]` and proxy URLs so LLM calls from the terminal behave identically to your agent's. Server pings every 25s keep idle sessions alive indefinitely.

See the [Terminal](terminal.md) page for full details, troubleshooting, and common patterns.

---

## Build (private repos)

Pass a GitHub personal access token via `org_secrets` when you trigger a build. ORB auto-injects it into the clone URL, no orb.toml change required.

```bash
curl -X POST https://api.orbcloud.dev/v1/computers/ID/build \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"org_secrets": {"GITHUB_TOKEN": "ghp_abc123"}}'
```

Create the token at <https://github.com/settings/tokens> with `repo` scope (classic) or fine-grained access to the specific repo.

---

## Auto-deploy on push

Wire up `git push` → ORB build. PAT is used-and-discarded, ORB never stores your GitHub token.

### Bootstrap

```
POST /v1/computers/{id}/github/bootstrap
Authorization: Bearer orb_YOUR_KEY
Content-Type: application/json

{
  "pat": "gho_...",
  "api_key": "orb_...",
  "branch": "main"
}
```

`pat` is required. `api_key` and `branch` are optional, ORB generates a scoped key and defaults the branch to `source.branch` if you omit them.

Commits `.github/workflows/orb-deploy.yml` to your repo, stashes `ORB_API_KEY` in the repo's Actions secrets, triggers the first build, discards the PAT.

### Disconnect

```
POST /v1/computers/{id}/github/disconnect
Authorization: Bearer orb_YOUR_KEY
Content-Type: application/json

{
  "pat": "gho_..."
}
```

Deletes the workflow file and the secret. Any ORB API key ORB generated during bootstrap stays in your keys list.

See [Auto-Deploy on Git Push](guides/auto-deploy.md) for full request/response shapes, PAT scopes, and the suggested input ladder (`gh auth token`, `git remote`, `git branch`).

---

## Usage

```
GET /v1/usage?start=2026-03-01T00:00:00Z&end=2026-03-15T23:59:59Z
GET /v1/usage?start=...&end=...&computer=<computer_id>
```

Returns runtime and disk GB-hours. Add `?computer=<id>` to scope to a single computer, without it, all computers in the org are summed.

| Field | Description |
|---|---|
| `runtime_gb_hours` | Sum of (RAM GB × hours agent was in RAM). Sleeping agents don't count, only Running/Frozen states bill. |
| `disk_gb_hours` | Sum of (disk GB × hours computer existed). |
| `checkpoint_cycles` | Number of times agents went to NVMe sleep in the period. |
| `computer_id` | Present when query was scoped to a single computer. |

**Important:** `runtime_gb_hours` is the time agents were actually occupying RAM, not wall-clock time. A 4 GB computer where the agent slept 90% of the time bills ~0.4 GB-hours per hour of wall time.

---

## Computer Stats

```
GET /v1/computers/{id}/stats
GET /v1/computers/{id}/stats?window=lifetime
```

Everything a customer dashboard needs for a single computer in one call: sleep efficiency, LLM call count, checkpoint totals, cost estimate. `window` is `30d` (default, matches `/v1/usage`) or `lifetime` (since computer creation).

| Field | Description |
|---|---|
| `window`, `period_start`, `period_end` | The range this report covers. |
| `wall_secs`, `active_secs`, `sleep_secs` | Wall-clock, RAM-time, NVMe-time in the window. |
| `sleep_pct`, `active_pct` | Percent of wall time asleep on NVMe vs. live in RAM. `sleep_pct` is the fraction of wall time you weren't billed for runtime. |
| `llm_calls` | Number of LLM roundtrips (≈ restores from NVMe). |
| `checkpoints`, `ckpt_full`, `ckpt_incremental`, `failures` | Checkpoint event counts. |
| `runtime_gb_hours`, `disk_gb_hours`, `est_cost_usd` | Billable totals + dollar estimate using the public rates ($0.005/GB-hr runtime + $0.05/GB-month disk). |
| `last_active_ago_secs` | Seconds since the agent last spawned or restored, or `null`. |
| `avg_restore_ms` | Mean wake time from NVMe in this window. |

---

## Webhooks

### Create

```
POST /v1/webhooks
```

```json
{"url": "https://your-server.com/webhook", "events": ["agent.spawned", "computer.created"]}
```

### List

```
GET /v1/webhooks
```

### Delete

```
DELETE /v1/webhooks/{id}
```

---

## Cron

Scheduled commands attached to a computer. You create jobs by writing `/agent/.orb/cron.json` in the sandbox, see [Cron in the orb.toml reference](config-reference.md#cron--scheduled-tasks) for the file format. The cloud scheduler fires each job at its due time and executes the command inside the computer's sandbox namespaces. These endpoints are read-only views of the jobs currently declared and the runs the scheduler has recorded.

### List Jobs

```
GET /v1/computers/{id}/cron
```

Returns every job currently declared in `cron.json` for this computer, with the resolved `next_fire_at` and most recent `last_fired_at`.

```json
{
  "jobs": [
    {
      "name": "heartbeat",
      "schedule": "*/5 * * * *",
      "command": "curl -sf localhost:8000/health",
      "env": {},
      "working_dir": null,
      "timeout_secs": 30,
      "enabled": true,
      "skip_if_running": true,
      "next_fire_at": "2026-04-19T04:05:00Z",
      "last_fired_at": "2026-04-19T04:00:00Z"
    }
  ]
}
```

### List Runs

```
GET /v1/computers/{id}/cron/runs?job={name}&limit={n}
```

| Query | Default | Description |
|---|---|---|
| `job` | (all) | Filter to a single job name. |
| `limit` | `50` | Max rows to return (1–500). |

Each run row includes `status` (`success`, `failure`, `timeout`, `skipped`, `error`, or `running`), `exit_code`, `duration_ms`, `fired_at`/`started_at`/`finished_at`, and captured `stdout` / `stderr` (truncated at 16 KB).

```json
{
  "runs": [
    {
      "id": "...",
      "job_id": "...",
      "fired_at": "2026-04-19T04:00:00Z",
      "started_at": "2026-04-19T04:00:00Z",
      "finished_at": "2026-04-19T04:00:00Z",
      "status": "success",
      "exit_code": 0,
      "duration_ms": 26,
      "stdout": "OK\n",
      "stderr": null,
      "error": null
    }
  ]
}
```

### Trigger Job

```
POST /v1/computers/{id}/cron/{name}/trigger
```

Run a cron job immediately, outside its normal schedule. The request body is ignored, the command, env, working_dir, and timeout are all taken from the job's declaration in `cron.json`. The run is recorded in `cron_runs` just like a scheduled firing, so it shows up in `/cron/runs` history.

Manual triggers **ignore** `enabled` and `skip_if_running`, they are explicit user overrides. The endpoint blocks until the command finishes (or hits `timeout_secs`), so expect requests to last up to the job's timeout.

```json
{
  "run_id": "...",
  "status": "success",
  "exit_code": 0,
  "stdout": "OK\n",
  "stderr": "",
  "duration_ms": 42,
  "timed_out": false
}
```

`status` is one of `success`, `failure`, `timeout`, or `error`. On runtime failure (`status: "error"`) the response is `502 Bad Gateway` with the same shape but `exit_code`, `stdout`, `stderr`, `duration_ms` null.

---

## Concepts

Background on how ORB manages agent memory and serves traffic. Useful when you're trying to understand *why* the API behaves the way it does.

### Agent lifecycle

ORB automatically manages agent memory. Sleep/wake happens transparently, you don't need to call it manually.

1. Your agent makes an LLM API call (via the routes in your `orb.toml`)
2. While waiting for the response, ORB sleeps the agent, freeing its RAM
3. When the response arrives, ORB wakes the agent and delivers the response
4. The agent continues where it left off, full state preserved

### Wake triggers

A sleeping agent wakes on:

1. **An LLM response arriving** on the proxied connection it opened (automatic, no config needed).
2. **An inbound HTTP request to the agent's own exposed port** at `https://{short-id}.orbcloud.dev/...`, only if your `orb.toml` has `[ports] expose = [...]`.
3. **A manual promote** via `POST /v1/computers/{id}/agents/promote`.

ORB's management API endpoints (`/files`, `/agents`, `/metrics`, `/computers`, `/terminal`) **do not wake the agent**. They read from the host filesystem and the runtime's in-memory registry directly. Listing files or polling state against a sleeping agent is free, the agent stays asleep. Use the WebSocket terminal or your agent's own HTTP endpoints for debugging.

### Memory

`runtime_mb` is your guaranteed RAM. Agents can temporarily burst above this when capacity is available. If you need consistent high memory, provision a larger computer.

Use `GET /v1/computers/{id}/agents` to check agent states. Sleeping agents wake automatically when their next API response arrives. To force-wake, use `POST /v1/computers/{id}/agents/promote`.

### Exposed ports

If your orb.toml includes `[ports] expose = [8000]`, your agent's port 8000 is accessible at:

```
https://{short_id}.orbcloud.dev
```

Where `short_id` is the first 8 characters of the computer ID. Your agent binds to its port as normal, ORB handles the routing.
