Appearance
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:
| Field | Meaning |
|---|---|
id | Pod id. |
name | Your name for it (2 to 30 chars, unique per account). |
os | Workload image name (ubuntu or mysql). |
version | Workload version (e.g. 24.04, 8.0). |
compute | Compute size (tiny / small / medium). |
storage | Storage size (tiny / small / medium / large). |
type | instance or database. You choose this indirectly by picking a workload. |
status | Running, Stopped (the console displays this one as "Off"), a transient status, or Failed / ResizeFailed. See statuses. |
username | Login user (ubuntu for Ubuntu pods, root for MySQL pods). |
domainName | The 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.) |
publicPort | The external port mapped to the pod's service. |
hostname | The pod's short hostname. Other pods on the same private network reach it by this name. |
privateIp | Private IP on the pod's network. |
privatePort | Internal service port (22 for SSH, 3306 for MySQL). |
serverId | The Region the pod runs in. |
networkId | Private network, if attached. |
firewallId | Firewall attached to the pod. |
snapshotSchedule / backupSchedule | Auto schedule: "", daily, weekly, or monthly. |
createdAt / updatedAt | Timestamps. |
List pods
http
GET /api/pods?page=1&limit=20&search=webPaginated. 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/podsBody (NewPodItem):
| Field | Required | Notes |
|---|---|---|
os | yes | Workload image: ubuntu or mysql. |
osVersion | yes | A version offered by that workload: 22.04 / 24.04 for Ubuntu, 5.7 / 8.0 for MySQL. |
compute | yes | tiny / small / medium. |
storage | yes | tiny / small / medium / large. |
serverId | yes | The Region id. Get valid ids from GET /open/config-options (servers). |
name | no | 2 to 30 chars, letters/digits/spaces/hyphens. Blank gets you a random name. Must be unique per account. |
sshPublicKey | no | A public key to install at boot. Instance pods only. Pasted keys are saved for reuse. |
sshKeyId | no | Use a key you already saved (wins over sshPublicKey). |
firewallId | no | Blank 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. |
networkId | no | Attach 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/:idReturns 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/:idBody (UpdatePodItem), all optional:
| Field | Effect |
|---|---|
name | Rename. Applies immediately. |
compute | Change compute size. Triggers an async Resize (the pod briefly stops and restarts). |
storage | Change disk size. Async Resize. |
firewallId | Swap the firewall. Applies without a resize. |
networkId | Move 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
networkIdis blocked with400while 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/:idResponse 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-passwordDatabase 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/telemetryThe 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-08from 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-ticketReturns 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." }| Status | When |
|---|---|
400 | Bad input, or an operation that isn't allowed in the pod's current state. |
402 | Under $3 credit on a create/clone/restore. |
403 | Pod limit reached, or your API key/role lacks the permission. |
404 | No such pod on your account. |
409 | The pod is busy with another operation. |