API Reference
The Betawings API lets you orchestrate Firecracker MicroVMs. VMs boot in under a second from a snapshot and are isolated at the hardware-virtualisation level — more secure than containers, more efficient than traditional VMs.
All endpoints return JSON. Error responses always include an error field.
Base URL
Response format
All successful responses are application/json. Errors use standard HTTP status codes with a JSON body:
{"error": "vm must be stopped before resizing cpu/memory"}
Authentication
Every request to /api/v1/* must include a bearer token in the Authorization header.
Authorization: Bearer <your-token>
Roles
Three org-scoped built-in roles are currently available to users.
Creating a token
curl -X POST https://api.betawings.com/api/v1/tokens/ \
-H "Authorization: Bearer <org-admin-token>" \
-H "Content-Type: application/json" \
-d '{"name":"ci-deploy","role":"creator","org_id":"<org-id>"}'
The raw token is only returned once at creation time — store it immediately.
The VM Object
| Field | Type | Description |
|---|---|---|
id | string | Unique VM identifier (16-char hex). |
name | string | Human-readable name. Must be unique. Used as the subdomain under *.onbw.net. |
status | string | running stopped error snapshotting |
image | string | Base disk image name the VM was created from. |
cpu_count | int | Number of vCPUs (1–32). |
memory_mb | int | RAM in mebibytes (64–65536). |
disk_mb | int | Disk size in MB. |
ip_address | string | Internal tap IP of the VM (10.200.x.x). Only set when running. |
node_id | string | Node the VM lives on. "local" means it runs on the control plane host. |
idle_timeout_seconds | int | Seconds of inactivity before the VM is auto-snapshotted and stopped. Default 300. |
has_snapshot | bool | Whether a Firecracker snapshot exists. If true, the next start is a fast (~1 s) resume. |
ssh_key_fingerprint | string | SHA-256 fingerprint of the injected SSH public key. |
terminal_url | string | URL to the in-browser terminal (requires VM to be running with TLS cert obtained). |
tags | object | Arbitrary key/value metadata. Example: {"env":"prod","team":"backend"}. |
org_id | string | Organisation the VM belongs to. |
created_at | string | RFC 3339 timestamp. |
started_at | string | RFC 3339 timestamp of most recent start. null if never started. |
stopped_at | string | RFC 3339 timestamp of most recent stop. |
last_activity_at | string | Timestamp of last reported activity (used by idle checker). |
error_msg | string | Set when status is error. Describes what failed. |
VM lifecycle
| State | What it means | Next actions |
|---|---|---|
| VM is being provisioned from the base image (remote node only). | Poll Get VM; transitions to stopped or error. | |
| running | Firecracker process is active; VM is accepting traffic. | Stop, Snapshot, Destroy, read Logs. |
| snapshotting | Firecracker memory snapshot is being written to disk. | Wait; transitions to stopped. |
| stopped | VM is not running. If has_snapshot: true, next Start resumes in ~300 ms. Otherwise cold boot (~10–20 s). | Start, Rebuild, Resize, Migrate, Destroy. |
| error | Last operation failed. Check error_msg and VM Logs. | Start (retry), Rebuild, Destroy. |
Virtual Machines
Create VM
Creates a new VM and starts it immediately. The VM is provisioned from the specified base image. If multi-node is configured, the CP auto-selects the node with the most free memory unless node_id is specified.
When targeting a remote node, provisioning is asynchronous — the API returns immediately with status: "deploying". Poll Get VM until status becomes "stopped" or "error".
Request body
| Field | Required | Description |
|---|---|---|
name | required | Unique VM name. Used as subdomain (name.onbw.net). Lowercase alphanumeric and hyphens. |
image | required | Base image name (see List Images). Example: "demo-44322". |
cpus | required | Number of vCPUs. Integer 1–32. |
memory_mb | required | RAM in MB. Integer 64–65536. |
ssh_public_key | optional | SSH public key to inject into /root/.ssh/authorized_keys. OpenSSH format. |
idle_timeout_seconds | optional | Seconds idle before auto-snapshot+stop. Default: 300. Range: 10–86400. |
org_id | optional | Org to create the VM in. Cloud-admin only; org-scoped callers are bound to their org automatically. |
node_id | optional | Target node ID, or "local" to force the CP host. Omit to auto-select. |
curl -X POST https://api.betawings.com/api/v1/vms/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-project",
"image": "demo-44322",
"cpus": 1,
"memory_mb": 512,
"ssh_public_key": "ssh-ed25519 AAAA... you@host",
"idle_timeout_seconds": 600
}'{
"id": "3f9a1c2b4e5d6f7a",
"name": "my-project",
"status": "running",
"image": "demo-44322",
"cpu_count": 1,
"memory_mb": 512,
"disk_mb": 4096,
"ip_address": "10.200.3.2",
"idle_timeout_seconds":600,
"has_snapshot": false,
"ssh_key_fingerprint": "SHA256:abc123...",
"terminal_url": "https://my-project.onbw.net/terminal/<token>/",
"node_id": "local",
"created_at": "2026-07-11T10:00:00Z",
"started_at": "2026-07-11T10:00:01Z"
}List VMs
Returns all VMs the caller can access. Org-scoped callers see only their org's VMs; cloud-admin sees all.
Query parameters
| Param | Description |
|---|---|
status | Filter by status. Values: running, stopped, deploying, error. |
tag | Filter by tag in key:value format. Example: ?tag=env:prod. |
curl https://api.betawings.com/api/v1/vms/?status=running \ -H "Authorization: Bearer $TOKEN"
Get VM
Returns a single VM by ID.
curl https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a \ -H "Authorization: Bearer $TOKEN"
Resize & Rename VM
Updates one or more mutable VM properties. All fields are optional — include only what you want to change.
Changing cpus or memory_mb requires the VM to be stopped. Growing disk_mb also requires stopped status; shrinking is not allowed.
| Field | Description |
|---|---|
name | New name. Must be globally unique. Takes effect immediately without a restart. |
cpus | New vCPU count (1–32). VM must be stopped. |
memory_mb | New RAM in MB (64–65536). VM must be stopped. |
disk_mb | New disk size in MB (1–102400). Grow-only. VM must be stopped. |
tags | Replace the full tag set. Pass an empty object to clear all tags. |
curl -X PATCH https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"cpus": 2, "memory_mb": 1024, "tags": {"env": "prod"}}'
Start VM
Starts a stopped VM. If a Firecracker snapshot exists (has_snapshot: true), the VM resumes from it in ~1 second. Otherwise it cold-boots (~10–20 seconds). Returns the updated VM object.
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/start \ -H "Authorization: Bearer $TOKEN"
Stop VM
Stops a running VM. The Firecracker process is sent SIGKILL. Any unsaved in-memory state is lost; the disk (rootfs.ext4) is fully preserved.
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/stop \ -H "Authorization: Bearer $TOKEN"
Snapshot VM
Creates a Firecracker memory snapshot of a running VM. The next start will restore from this snapshot (~1 s) rather than cold-booting. By default the VM is stopped after the snapshot; pass keep_running: true to leave it running (useful for periodic checkpoints).
Firecracker snapshots are host-specific. They encode CPU register state in the current host's microarchitecture format and cannot be migrated to a different physical host. If you migrate the VM, the snapshot is discarded automatically and the next boot is a cold boot.
| Field | Description |
|---|---|
keep_running | Boolean. Default false — VM is stopped after snapshot. Set true to keep it running. |
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/snapshot \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"keep_running": true}'
Rebuild VM
Replaces the VM's disk with a fresh copy of its base image. All data on the disk is permanently erased. The VM must be stopped. Any existing snapshot is cleared.
Destructive operation. All data on the VM's disk is lost. This cannot be undone.
| Field | Description |
|---|---|
ssh_public_key | SSH public key to inject into the fresh disk. Required if you want SSH access after rebuild (the original key is not preserved). |
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/rebuild \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ssh_public_key": "ssh-ed25519 AAAA... you@host"}'
Migrate VM
Moves a VM's disk to a different node. The VM's rootfs.ext4 is streamed from the source host through the CP to the target host. After migration the VM can be started on the new host with a cold boot.
The VM must be stopped before migrating. The operation is crash-safe: the DB node_id is only updated after the target has received the full rootfs.
Firecracker snapshots are not transferred — they are CPU-architecture-specific and cannot be restored cross-host. Snapshot state is cleared as part of the migration; the first start on the new host will always be a cold boot.
| Field | Description |
|---|---|
target_node_id | required Destination node ID, or "local" to move the VM to the control-plane host. |
# Move a local VM onto a remote node
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/migrate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"target_node_id": "8d82890e3c0fb5aa"}'# Pull a VM from a remote node back to local
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/migrate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"target_node_id": "local"}'Destroy VM
Permanently deletes a VM — the Firecracker process, disk image, snapshots, and DB record are all removed. This cannot be undone.
Returns 204 No Content on success.
curl -X DELETE https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a \ -H "Authorization: Bearer $TOKEN"
Set Idle Config
Updates the VM's idle timeout. When the VM has had no activity for this many seconds, the idle checker automatically creates a Firecracker memory snapshot and stops the VM — freeing its CPU and RAM. The next incoming HTTPS, SSH, or HTTP request to the VM's domain triggers an automatic resume from snapshot (~300 ms).
What counts as activity: any proxied connection through the Betawings gateway (HTTPS, SSH, HTTP), or an explicit Report Activity call from inside the VM. The timer resets on each event.
| Field | Description |
|---|---|
idle_timeout_seconds | required Integer 10–86400 (10 seconds to 24 hours). Default is 300 (5 minutes). |
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/idle-config \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"idle_timeout_seconds": 1800}'
Get Activity Info
Returns how long the VM has been idle and when it will be shut down by the idle checker.
curl https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/activity \ -H "Authorization: Bearer $TOKEN"
{
"last_activity_at": "2026-07-11T10:05:00Z",
"idle_for_seconds": 120,
"will_shutdown_in_seconds": 180
}Report Activity
Resets the idle timer, signalling that the VM is still in use. Call this from inside the VM (or from a client) when work is happening, to prevent an idle shutdown. The source field is informational only.
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/activity \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"source": "user-request", "packet_count": 42}'
Get VM Logs
Returns the last N lines of the VM's Firecracker log. The log contains kernel boot messages, device initialisation output, and any text written by the guest to the serial console. Useful for diagnosing boot failures and startup scripts.
| Query param | Description |
|---|---|
lines | Number of lines to return. Default 100, max 10000. |
curl "https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/logs?lines=200" \ -H "Authorization: Bearer $TOKEN"
Logs are available whether the VM is running or stopped. They persist across restarts and snapshots for the lifetime of the VM.
SSH Access
Every VM created with an ssh_public_key is reachable via the Betawings SSH proxy at api.betawings.com:2222. The connection is forwarded byte-for-byte to your VM — your private key never touches the gateway.
Overview
Betawings uses SSH ProxyJump: your SSH client connects to the gateway at api.betawings.com:2222, which resolves the target VM by name and forwards the connection directly. The gateway never terminates or inspects the SSH session.
The SSH key you pass when creating the VM is injected into /root/.ssh/authorized_keys on the guest disk. The gateway stores only the SHA-256 fingerprint for routing — it never holds the private or public key material for authentication purposes.
Connect directly without a config file using the -J ProxyJump flag:
ssh -J root@api.betawings.com:2222 root@my-project.onbw.net
SSH Config
Add the following block to ~/.ssh/config for seamless access to all your VMs:
# Wings gateway Host wings-gw HostName api.betawings.com Port 2222 User root IdentityFile ~/.ssh/your-key # All VMs on *.onbw.net jump through the gateway Host *.onbw.net User root ProxyJump wings-gw IdentityFile ~/.ssh/your-key
Once configured, connect with just:
ssh my-project.onbw.net
The hostname my-project must match the name field of your VM exactly.
Wake on SSH
If the VM is currently sleeping (stopped with a snapshot), the SSH proxy automatically wakes it before forwarding the connection. Your SSH client holds the TCP connection open while the VM resumes from snapshot — typically under 300 ms. No retry is needed on the client side.
An SSH connection to a sleeping VM resets the idle timer as soon as the VM wakes. The VM will not be shut down again until it has been idle for the full idle_timeout_seconds after the session ends.
The VM must have been created with ssh_public_key. If no key was provided at creation time, SSH access is not available without a Rebuild.
Custom Domains
By default every VM is reachable at name.onbw.net. Custom domains let you route your own domain to a VM with ownership verified via a DNS TXT record.
The typical flow is: Add domain → create the TXT record in your DNS provider → Verify → point your domain's A/AAAA record to 51.159.66.179. Wings will begin routing HTTPS traffic to the VM as soon as DNS propagates. TLS is end-to-end: Wings reads only the SNI hostname from your HTTPS connections and does not terminate or inspect the TLS session.
List Domains
curl https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/domains \ -H "Authorization: Bearer $TOKEN"
Add Domain
Registers a custom domain for a VM and returns a DNS challenge value. The domain is not yet verified — create the TXT record and call Verify.
curl -X PUT https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/domains/myapp.example.com \ -H "Authorization: Bearer $TOKEN"
{
"domain": "myapp.example.com",
"vm_id": "3f9a1c2b4e5d6f7a",
"status": "pending_verification",
"txt_record": "_wings-challenge.myapp.example.com",
"txt_value": "wings-a1b2c3d4...",
"verify_hint": "Set TXT record, then POST /api/v1/vms/.../domains/.../verify"
}Verify Domain
Checks that the DNS TXT record _wings-challenge.{domain} contains the challenge value issued by Add Domain. On success the domain is marked verified and Wings begins routing it to the VM.
curl -X POST https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/domains/myapp.example.com/verify \ -H "Authorization: Bearer $TOKEN"
Remove Domain
Removes the custom domain association. Wings stops routing that domain to the VM. Returns 204 No Content.
curl -X DELETE https://api.betawings.com/api/v1/vms/3f9a1c2b4e5d6f7a/domains/myapp.example.com \ -H "Authorization: Bearer $TOKEN"
API Tokens
Create Token
Creates a new API token. The raw token value is returned once — it is not stored and cannot be retrieved later. Store it securely immediately.
| Field | Description |
|---|---|
name | required Human-readable label for this token. |
role | required One of: org-admin, creator, reader. (Platform-level roles are only assignable by a cloud-admin.) |
org_id | Required for org-scoped roles (org-admin, creator, reader). |
expires_at | Optional RFC 3339 expiry timestamp. Token is rejected after this time. |
curl -X POST https://api.betawings.com/api/v1/tokens/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-pipeline",
"role": "creator",
"org_id": "a1b2c3d4e5f6",
"expires_at": "2027-01-01T00:00:00Z"
}'{
"token": "bwt_a1b2c3...<only shown once>",
"id": "7c8d9e0f1a2b",
"name": "ci-pipeline",
"role": "creator",
"org_id": "a1b2c3d4e5f6",
"scope_type": "org",
"created_at": "2026-07-11T10:00:00Z"
}List Tokens
Returns all tokens visible to the caller. Cloud-admin sees all tokens; org-scoped callers see only tokens belonging to their org. Raw token values are never returned.
curl https://api.betawings.com/api/v1/tokens/ \ -H "Authorization: Bearer $TOKEN"
Delete Token
Revokes a token immediately. All subsequent requests using that token receive 401 Unauthorized. Returns 204 No Content.
curl -X DELETE https://api.betawings.com/api/v1/tokens/7c8d9e0f1a2b \ -H "Authorization: Bearer $TOKEN"
Best practices: use the most restrictive role that covers your use case (prefer creator or reader over org-admin for automated pipelines). Set expires_at on all CI/CD tokens. Rotate tokens that may have been exposed — deletion is instant.
Error Reference
All errors return a JSON body with an error field describing what went wrong. Common HTTP status codes:
| Status | Meaning | Common cause |
|---|---|---|
400 | Bad Request | Missing required field, invalid value (e.g. name contains uppercase), or constraint violation (e.g. resizing a running VM). |
401 | Unauthorized | Missing or invalid bearer token. Token may have expired or been revoked. |
403 | Forbidden | Token is valid but lacks permission for this action (e.g. reader attempting to create a VM). |
404 | Not Found | VM, domain, or token with that ID does not exist, or belongs to a different org. |
409 | Conflict | Name already in use (VM or domain). Domain ownership already claimed by another VM. |
429 | Too Many Requests | VM creation rate limit exceeded. Back off and retry. |
500 | Internal Error | Unexpected server error. Check VM logs; if persistent, contact support. |
System
Health Check
No authentication required. Returns platform health and VM counts. Use this for uptime monitoring.
curl https://api.betawings.com/health
{
"status": "ok",
"vms_running": 4,
"vms_stopped": 12,
"vms_total": 16
}List Images
Returns available base disk images. The name field is what you pass as image when creating a VM.
curl https://api.betawings.com/api/v1/images \ -H "Authorization: Bearer $TOKEN"
[{
"name": "demo-44322",
"size_bytes": 4294967296
}]