ORCHD

API reference

Base URL is your instance's control API (https://api.<your-domain>, or http://127.0.0.1:8080 locally). Every /v1/* endpoint needs a key — see Keys & access — presented as Authorization: Bearer <key> or X-API-Key: <key>.

Roles below: open = no key · any key = a readonly key suffices · admin = admin only (every POST, PUT and DELETE).

Errors are { "error": "…" } with the status codes listed here.

Health

GET /healthz — open

Liveness. The only endpoint that never requires a key.

{ "status": "ok", "auth": true }

Projects

POST /v1/projects — admin

Create a project and its workloads. Three mutually exclusive ways to say what to create: explicit workloads, a registered template, or a frozen image ("template@version"). An empty body creates one primary database workload. region defaults to the default region.

{
  "name": "acme",                    // optional
  "region": "local",                 // optional
  "template": "acme",                // optional — create from a template
  "image": "acme@v3",                // optional — create from a frozen image
  "delta": { "web/index.html": "…" }, // optional — files overlaid on the base
  "deleted": ["web/old.html"],        // optional — base paths to remove
  "workloads": [                     // optional (default: one primary db workload)
    { "preset": "vite" },
    { "preset": "api" }
  ]
}

Response 201 — the project with its workloads, each carrying state, routes, endpoints, subroutes, resource caps and (for database workloads) anon_key / service_role_key. Workloads start in provisioning; poll or watch events for running.

GET /v1/projects — any key

All projects with their workloads.

GET /v1/projects/{id} — any key

One project.

DELETE /v1/projects/{id} — admin

Stop and remove the project, every workload, their routes and their on-disk data. 204. Destructive.

POST /v1/projects/{id}/start · /stop · /restart — admin

Lifecycle for every workload in the project.

PUT /v1/projects/{id}/env — admin

Set environment variables on every workload in the project. Reboots them.

{ "env": { "LOG_LEVEL": "debug" } }

Workloads

POST /v1/projects/{id}/workloads — admin

Add a workload. Use a preset, or set the fields explicitly (explicit wins).

{
  "preset": "vite",          // or omit and set type/image/port
  "type": "rapidnative-dev",
  "name": "web",             // role; also the route suffix
  "image": "rn-vite:dev",
  "port": 8080,              // the port inside the container
  "memory_mb": 512,
  "cpus": 1.0,
  "env": { "NODE_ENV": "development" }
}

GET /v1/workloads/{id} — any key

One workload: state, caps, keys, routes, endpoints, last_seen.

DELETE /v1/workloads/{id} — admin

Stop and remove the workload, its routes and its data. 204.

POST /v1/workloads/{id}/start · /stop · /restart — admin

Boot, halt or reboot one workload. A stopped workload is not woken by traffic.

PUT /v1/workloads/{id}/env — admin

Replace the workload's environment variables. Reboots it to apply.

PUT /v1/workloads/{id}/image — admin

Move a workload booted from a frozen image to another version of its template's image. Its data volume and sibling workloads are untouched. A running workload is recreated on the new image now; a suspended one stays asleep and is recreated on the new image at its next wake, so moving many workloads boots none of them. 400 when that version doesn't exist or has no image for the workload's workspace.

{ "version": "v56" }   // -> { "image_version": "v56", "applied": "next_wake" }  // or "restarted", "unchanged"

POST /v1/workloads/{id}/keepwarm — admin

Toggle always-on. Enabling boots it now and exempts it from scale-to-zero.

{ "enabled": true }   // -> { "keep_warm": true }

GET /v1/workloads/{id}/stats — any key

Live memory and CPU. Empty when not running.

{ "mem_usage": "76.2MiB / 384MiB", "mem_perc": "19.8%", "cpu_perc": "1.0%" }

GET /v1/workloads/{id}/logs — any key

Recent stdout + stderr. Params: tail=<n> (default 200).

{ "logs": "…last N lines…" }

Workload files

GET/v1/workloads/{id}/fsany key — list files in the workload's tree
GET/v1/workloads/{id}/fs/file?path=…any key — file contents as text/plain
PUT/v1/workloads/{id}/fs/file?path=…admin — write a file (raw body, 16 MiB cap)
DELETE/v1/workloads/{id}/fs/file?path=…admin — delete a file. 204

Routes (domains)

POST /v1/workloads/{id}/routes — admin

Attach a hostname. Point the DNS at the gateway first; a certificate is issued on the first HTTPS request.

{ "host": "app.customer.com" }

DELETE /v1/routes?host=… — admin

Detach a hostname. 204.

Templates

GET /v1/templates — any key

Registered templates as { name: path }.

PUT /v1/templates — admin

Register (or re-point) a template.

{ "name": "acme", "path": "/srv/templates/acme" }

GET /v1/templates/{name} — any key

The parsed orchd.json manifest.

GET /v1/templates/{name}/files — any key

File list, or one file's contents with ?path=….

GET /v1/templates/{name}/bundle — any key

tar.gz of the template tree, minus derived paths.

POST /v1/templates/{name}/build — admin

Freeze the template into a new auto-incremented version. Returns 201 with the image record (tarball path, per-workspace docker tags, workload shape). See Images.

Built images

Versioned freezes of a template.

GET/v1/built-imagesany key — list
GET/v1/built-images/{name}/{version}/bundleany key — download the base tarball
POST/v1/built-images/{name}/{version}/pushadmin — re-tag and push to the configured registry → { "registry": { workspace: ref } }
GET/v1/built-images/{name}/{version}/specany key — the self-contained import spec
POST/v1/built-images/importadmin — record an image from another instance (post the spec)
DELETE/v1/built-images/{name}/{version}admin — delete the record and tarball. 204

Docker images

Tags on a region's Docker daemon. Requires the Docker driver; the process driver answers 501. region is optional everywhere (empty = default region).

GET /v1/images — any key

List, excluding dangling layers. digest is the stable identity to pin against.

[{ "repository": "rn-vite", "tag": "dev", "ref": "rn-vite:dev", "id": "a1b2c3d4e5f6",
   "digest": "sha256:9f86d081…", "size": "412MB", "created_at": "2 days ago" }]

POST /v1/images/pull — admin

Pull a ref. Blocks until the pull finishes and returns the CLI output.

{ "ref": "ghcr.io/acme/app:1.2.0", "region": "" }

DELETE /v1/images?ref=… — admin

Remove by ref or id. Fails if a container uses it unless force=true. 204.

Backups

POST /v1/workloads/{id}/backups — admin

Snapshot one workload now.

{ "id": "wid__20260719T…", "workload_id": "wid", "created_at": "…", "size_bytes": 4797652 }

POST /v1/projects/{id}/backups — admin

Snapshot every workload in a project. Returns an array.

GET /v1/backups — any key

All backups, newest first.

GET /v1/workloads/{id}/backups — any key

Backups for one workload.

POST /v1/workloads/{id}/restore — admin

Replace the workload's data with a backup, then reboot it.

{ "backup_id": "wid__20260719T…" }   // -> { "status": "restored" }

DELETE /v1/backups/{id} — admin

Delete a backup. 204.

POST /v1/system/backup — admin

Snapshot the control-plane state off-box under the reserved key _control-plane. Runs on the backup schedule too.

Regions

GET /v1/regions — any key

[{ "id": "local", "name": "local", "docker_host": "", "is_default": true, "created_at": "…" }]

POST /v1/regions — admin

Create one; the id is a slug of the name.

{ "name": "EU West", "docker_host": "tcp://node2:2375" }

DELETE /v1/regions/{id} — admin

Delete (never the default — set another first). 204.

POST /v1/regions/{id}/default — admin

Make it the default. → { "default": "eu-west" }

API keys

GET /v1/keys — any key

Metadata only, never secrets.

POST /v1/keys — admin

Mint a key; the plaintext is returned exactly once.

{ "name": "ci-bot", "role": "readonly" }

DELETE /v1/keys/{id} — admin

Revoke. 204.

Settings

Runtime configuration. Secrets are stored server-side and never returned.

GET /v1/settings — any key

{
  "instance_name": "acme cloud",
  "backup": { "type": "s3", "endpoint": "…", "bucket": "…", "region": "…", "access_key": "…" },
  "backup_secret_set": true,
  "webhook": { "url": "" },
  "metrics": { "type": "nop" },
  "registry": "ghcr.io/acme"
}

PUT /v1/settings/name — admin

Name this deployment (shown in the panel). { "instance_name": "acme cloud" }

PUT /v1/settings/registry — admin

The registry prefix built images are pushed under. { "registry": "ghcr.io/acme" }

PUT /v1/settings/backup — admin

The backup destination. Leave secret_key blank on an s3 target to keep the existing secret.

{
  "type": "s3",                 // "local" | "s3"
  "endpoint": "https://<account>.r2.cloudflarestorage.com",
  "bucket": "backups", "region": "auto", "prefix": "backups",
  "access_key": "…", "secret_key": "…"
}

PUT /v1/settings/webhook — admin

Event webhook URL; blank disables it. { "url": "https://example.com/hooks" }

The same URL receives two synchronous events, sent with X-Webhook-Key when an API key is set:

  • domain.not_found { "host" }: a request arrived for a host with no route. orchd holds it while the receiver re-creates the project (ORCHD_REPROVISION_HOOK_TIMEOUT, default 30s).
  • workload.image_behind { "project_id", "workload_id", "template", "image_version", "latest_version" }: a sleeping workload is about to wake on an image older than its template's newest. orchd waits up to ORCHD_WAKE_HOOK_TIMEOUT (default 20s, 0 disables) so the receiver can write files and call PUT /v1/workloads/{id}/image first, then wakes it either way. Each project is asked once per newest version; a 2xx that leaves the workload behind is not asked again until a newer image ships, and a failed call is retried after 10 minutes.

PUT /v1/settings/metrics — admin

Metrics sink. { "type": "http", "url": "https://collector/metrics" } — type is nop, log or http.

Metrics, events, metadata

GET /v1/metrics — any key

Live fleet snapshot.

{ "time": "…", "projects": 3, "workloads": 6, "running": 2, "suspended": 4,
  "mem_mb_allocated": 768 }

GET /v1/events — any key

Recent control-plane events, newest first. Params: limit=<n> (default 100).

[{ "id": "…", "time": "…", "type": "project.created", "project_id": "abc123", "message": "" }]

GET /v1/presets — any key

Preset names available on this instance.

GET /v1/info — any key

What this instance is running — the fastest way to confirm a deployment.

{
  "instance_name": "acme cloud",
  "driver": "docker+runsc", "region": "local", "base_domain": "example.com",
  "public_url": "https://example.com",
  "idle_timeout": "5m0s", "image": "tinbase:0.10.0", "rate_limit_per_min": 600,
  "limits": { "tinbase_mem_mb": 384, "tinbase_cpus": 0.5,
              "dev_mem_mb": 512, "dev_cpus": 1, "pids_limit": 512 },
  "backups": { "enabled": true, "interval": "24h0m0s", "retain": 7 },
  "images_supported": true,
  "metrics": { "type": "nop" },
  "presets": ["api", "expo", "tinbase", "vite"]
}

Internal

GET /internal/tls-allow?domain=… — open

Caddy's on-demand-TLS gate: 200 for admin/api on a configured base domain and for any host in the route table, 403 otherwise. Not for general clients — see Routing & TLS.

from the same team

Related projects

ORCHD is built by the makers of these open-source tools and products.