Naming. Agents pick the workload name, and it becomes the public subdomain directly (
<name>.h4a.site) the momentexposeis called. Choose something descriptive and memorable (solar-dashboard,recipe-api,demo-2026) — not a UUID or random slug. The platform never generates names on your behalf.
Every verb is idempotent by name. Retrying is always safe. The MCP tool surface and the REST surface are generated from the same Go interface — adding a capability to one adds it to the other.
POST /api/provision · MCP tool: provision
If you have a git repo, you probably want
deploy, notprovision.deployprovisions the same kind of VM AND ships your code on it (Docker container behind Caddy).provisiongives you a bare Hetzner VM with SSH access — useful when you need to install something interactively, run a long-lived process not in a repo, or experiment. If you callprovisionplanning to SSH in and run your app yourself, you're bypassing what h4a is for.
Create (or retrieve if it already exists) a workload by name.
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | DNS-safe: [a-z0-9][a-z0-9-]{0,62} |
| tenant | string | no | defaults to "default" in v0 |
| size | enum | no | nano/small/medium/large; defaults to small |
Returns { name, tenant, ipv4, ssh_command, status }. On a second call with the same name, returns the existing VM's IPv4 — no duplicate is created.
The ssh_command is ssh root@<ipv4> by default, or ssh -i <path> root@<ipv4> if the operator set H4A_SSH_KEY_HINT (recommended — see /docs/ssh-access.md). VMs trust whichever public keys the operator registered with Hetzner via HCLOUD_SSH_KEY_IDS.
POST /api/destroy · MCP tool: destroy
Destroy a workload by name. Idempotent — calling on a non-existent workload is a no-op success.
| Field | Type | Required |
|---|---|---|
| name | string | yes |
| tenant | string | no |
| force | bool | no (v0: no-op; M1+ bypasses soft-delete grace) |
POST /api/info · MCP tool: info
Return the current state from the control plane's store. No provider round-trip.
Returns { name, tenant, ipv4, status, created_at }. Returns ErrNotFound (404/status: "not-found" over MCP) if the workload doesn't exist.
POST /api/expose · MCP tool: expose
Attach a reachable URL to a running workload. See /docs/expose for full details.
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | yes | |
| tenant | string | no | |
| mode | enum | no | public (default): <name>.h4a.site over HTTPS via Caddy+LE on the VM. private: attach a Netbird peer; returns a routable hostname on the mesh. |
| port | int | no | Application port on the VM (default 8080). |
Returns { name, tenant, mode, url }.
POST /api/deploy · MCP tool: deploy
One call from a git repo to a live URL. Platform auto-detects tier (static vs dynamic). See /docs/deploy for full details including request/response schemas, tier-detection rules, and the .h4a.yaml override.
deployis the default path. If you have a git repo, calldeploy— don't callprovisionand SSH in to run things by hand.deploycreates the VM AND ships your app on it (Docker container behind Caddy for dynamic, Bunny CDN upload for static).provisionalone leaves you with a bare Linux box and no app.
Both tiers are live: static publishes to Bunny CDN in ~15s; dynamic spawns a Hetzner VM with Docker + Caddy via cloud-init, blue-green by default.
| Status | Meaning |
|---|---|
| provisioning | VM is being created |
| running | VM is up and reachable |
| destroying | Destroy in flight |
| destroyed | Soft-deleted; pending grace period (M1+) |
| failed | Create or destroy failed; destroy to clean up |