Skip to content

Pods API

Everything under /api/pods needs a bearer token:

http
Authorization: Bearer <jwt-or-apikey>

The token can be a login JWT or an API key. Pod names are unique per account. Creating, cloning, and restoring a pod each need at least $3 of credit (otherwise 402) and count against your pod limit (otherwise 403).

A lot of this is asynchronous

Create, resize, delete, clone, and restore all return right away, then the pod works through a transient status (Initializing, Resizing, Restoring, Cloning, Deleting) before it settles back on Running or Stopped. If an operation cannot complete, the pod lands in Failed (or ResizeFailed). If you need to know when it's done, poll describe. Starting a second operation while one is already running gets you a 409.

The pod object

Most endpoints return a Pod. The fields that matter:

FieldMeaning
idPod id.
nameYour name for it (2 to 30 chars, unique per account).
osWorkload image name (ubuntu or mysql).
versionWorkload version (e.g. 24.04, 8.0).
computeCompute size (tiny / small / medium).
storageStorage size (tiny / small / medium / large).
typeinstance or database. You choose this indirectly by picking a workload.
statusRunning, Stopped (the console displays this one as "Off"), a transient status, or Failed / ResizeFailed. See statuses.
usernameLogin user (ubuntu for Ubuntu pods, root for MySQL pods).
domainNameThe pod's public host - connect to this from the internet. Pods in the same Region share this host; your pod's publicPort is the part that's unique. (Not a custom domain you mapped.)
publicPortThe external port mapped to the pod's service.
hostnameThe pod's short hostname. Other pods on the same private network reach it by this name.
privateIpPrivate IP on the pod's network.
privatePortInternal service port (22 for SSH, 3306 for MySQL).
serverIdThe Region the pod runs in.
networkIdPrivate network, if attached.
firewallIdFirewall attached to the pod.
snapshotSchedule / backupScheduleAuto schedule: "", daily, weekly, or monthly.
createdAt / updatedAtTimestamps.

List pods

http
GET /api/pods?page=1&limit=20&search=web

Paginated. search matches on name, workload, and status. Response:

json
{
  "data": [ { "id": "...", "name": "web", "status": "Running", "...": "..." } ],
  "total": 3,
  "page": 1,
  "limit": 20,
  "totalPages": 1
}

Create a pod

http
POST /api/pods

Body (NewPodItem):

FieldRequiredNotes
osyesWorkload image: ubuntu or mysql.
osVersionyesA version offered by that workload: 22.04 / 24.04 for Ubuntu, 5.7 / 8.0 for MySQL.
computeyestiny / small / medium.
storageyestiny / small / medium / large.
serverIdyesThe Region id. Get valid ids from GET /open/config-options (servers).
nameno2 to 30 chars, letters/digits/spaces/hyphens. Blank gets you a random name. Must be unique per account.
sshPublicKeynoA public key to install at boot. Instance pods only. Pasted keys are saved for reuse.
sshKeyIdnoUse a key you already saved (wins over sshPublicKey).
firewallIdnoBlank creates a new allow-all firewall for this pod - it is open to everyone until you tighten it, and it counts against your firewall limit.
networkIdnoAttach a private network (must be in the same Region as the pod). Blank creates a new private network just for this pod - two pods created without a network can not reach each other privately. It counts against your network limit.
bash
curl -X POST https://cloud-api.microapps.io/api/pods \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "os": "ubuntu",
    "osVersion": "24.04",
    "compute": "small",
    "storage": "tiny",
    "serverId": "<region-id>"
  }'

Response 201:

json
{
  "pod": { "id": "...", "name": "cool-otter", "status": "Initializing", "...": "..." },
  "password": "s3cr3t-shown-once"
}

password is shown exactly once

The password field is only non-empty when there's a login secret we can't show you again:

  • an instance pod created without an SSH key - a one-time login password for the username, or
  • a database pod - the generated database password.

If you added an SSH key, or on any other call, password is "". Save it now. There's no endpoint to fetch it later (though database pods can reset it).

The pod comes back Initializing and finishes provisioning in the background.

Describe a pod

http
GET /api/pods/:id

Returns the Pod. 404 if it isn't yours.

Start, stop, restart

http
GET /api/pods/:id/action/:action

:action is one of start, stop, restart. Response 204.

Yes, this is a GET that changes state

It's a quirk of the API. GET .../action/stop really does stop the pod. Don't let a link prefetcher loose on it.

Returns 409 if the pod is mid-operation ("Pod is currently <status>; try again once the operation completes.").

bash
curl https://cloud-api.microapps.io/api/pods/$POD_ID/action/restart \
  -H "Authorization: Bearer $TOKEN"

Update a pod (rename, resize, reassign)

http
PUT /api/pods/:id

Body (UpdatePodItem), all optional:

FieldEffect
nameRename. Applies immediately.
computeChange compute size. Triggers an async Resize (the pod briefly stops and restarts).
storageChange disk size. Async Resize.
firewallIdSwap the firewall. Applies without a resize.
networkIdMove to another private network (same Region). Async Resize.
bash
curl -X PUT https://cloud-api.microapps.io/api/pods/$POD_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "compute": "medium" }'

Response 200 with the updated Pod. Notes:

  • Changing networkId is blocked with 400 while any domains are still mapped to the pod. Unmap them first.
  • A compute/storage/network change claims the pod; if another operation already owns it you get 409.

Delete a pod

http
DELETE /api/pods/:id

Response 204. Teardown happens in the background.

Blocked while domains are mapped

If any custom domains still point at the pod, delete returns 400 ("Pod is still mapped to N domains"). Unmap them first.

Reset the database password

http
POST /api/pods/:id/reset-password

Database pods only. Generates a new database password, applies it, and returns it:

json
{ "password": "the-new-one" }

On an instance pod this returns 400 ("Pod is not a database type"). Instance pods don't have a resettable login password - see Connecting via SSH.

Live telemetry

http
GET /api/pods/:id/telemetry

The latest sample:

json
{
  "cpuUsagePercent": 4.2,
  "memoryUsagePercent": 31.0,
  "diskUsagePercent": 12.5,
  "networkInBytes": 84213,
  "networkOutBytes": 15602,
  "processCount": 37
}

404 until the pod has reported at least once ("No metrics found for pod: ...").

Telemetry history

http
GET /api/pods/:id/telemetry/history?from=2026-07-01&to=2026-07-08

from and to accept RFC3339 (2026-07-08T00:00:00Z) or plain dates (YYYY-MM-DD, to counts to end of day). Default window is the last 7 days, and the window is capped at 30 days. Response is an array of hourly points:

json
[
  {
    "timestamp": "2026-07-08T14:00:00Z",
    "cpuUsagePercent": 6.1,
    "memoryUsagePercent": 33.4,
    "diskUsagePercent": 12.5,
    "networkInBytes": 1048576,
    "networkOutBytes": 262144
  }
]

Web console (Shell)

Open an in-browser shell without SSH. Instance pods only.

http
POST /api/pods/:id/console-ticket

Returns a single-use ticket, good for 30 seconds:

json
{ "ticket": "5f2c...-uuid" }

Then open a WebSocket with the ticket:

wss://cloud-api.microapps.io/api/pods/:id/console?ticket=<ticket>

On a database pod, console-ticket returns 400 ("Console is available only for instance pods").

Errors

Every error is a single-field JSON body; the HTTP status carries the category:

json
{ "message": "Insufficient credit. You need at least $3 on your account to proceed." }
StatusWhen
400Bad input, or an operation that isn't allowed in the pod's current state.
402Under $3 credit on a create/clone/restore.
403Pod limit reached, or your API key/role lacks the permission.
404No such pod on your account.
409The pod is busy with another operation.

See also

Built for the long tail.