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}/fs | any 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-images | any key — list |
GET | /v1/built-images/{name}/{version}/bundle | any key — download the base tarball |
POST | /v1/built-images/{name}/{version}/push | admin — re-tag and push to the configured registry → { "registry": { workspace: ref } } |
GET | /v1/built-images/{name}/{version}/spec | any key — the self-contained import spec |
POST | /v1/built-images/import | admin — 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 toORCHD_WAKE_HOOK_TIMEOUT(default 20s,0disables) so the receiver can write files and callPUT /v1/workloads/{id}/imagefirst, 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.