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. Each route also needs a permission, listed with the endpoint. 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 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:
json
{
"id": "6a4d5b83b7006ccae6391810",
"name": "my-app",
"os": "ubuntu",
"version": "24.04",
"storage": "tiny",
"compute": "small",
"username": "ubuntu",
"privateIp": "10.20.0.5",
"hostname": "i-6a4d5b83b7006ccae6391810",
"domainName": "your-pod.microapps.io",
"privatePort": 22,
"publicPort": 3001,
"status": "Running",
"type": "instance",
"networkId": "6a4d5b83b7006ccae6391a22",
"serverId": "6a4d5b83b7006ccae639170f",
"firewallId": "6a4d5b83b7006ccae6391b07",
"accountId": "6a4d5b83b7006ccae639100a",
"snapshotSchedule": "daily",
"backupSchedule": "daily",
"backupKeep": 7,
"createdAt": "2026-09-01T12:00:00Z",
"updatedAt": "2026-09-14T08:31:07Z"
}| 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 | See statuses below. |
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 generated 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 the pod is attached to. |
firewallId | Firewall attached to the pod. |
snapshotSchedule / backupSchedule | Auto schedule: daily, weekly, or monthly. Both are omitted when the schedule is off. |
backupKeep | How many automatic backups the backup schedule keeps once paid backups start, from 1 to 365. Set it alongside the schedule, as {"schedule": "daily", "keep": 7} on PUT /api/pods/:id/backup-schedule. Omitted until you set one, which means 5. |
createdAt / updatedAt | Timestamps. updatedAt is null until the pod is first changed. |
Statuses
| Status | Meaning |
|---|---|
Initializing | Being provisioned. |
Running | Up. |
Stopped | Off. The console labels this one "Off". |
Resizing | A compute, storage, or network change is being applied. |
Restoring | A snapshot or backup restore is running. |
Cloning | A clone is being copied into this pod. |
Deleting | Teardown is running. |
Failed | Provisioning, restore, or clone could not complete. |
ResizeFailed | A resize could not complete. |
Resizing, Restoring, Cloning, Deleting and Initializing are owned by a background worker: while the pod is in one of them, start/stop/restart, resize, and delete all return 409. See also statuses in the pods concept.
List pods
http
GET /api/pods?page=1&limit=20&search=my-appNeeds pods:read. Paginated, newest first. search matches on name, os, and status. limit is capped at 100.
bash
curl "https://cloud-api.microapps.io/api/pods?limit=2" \
-H "Authorization: Bearer $MICROAPPS_API_KEY"json
{
"data": [
{
"id": "6a4d5b83b7006ccae6391810",
"name": "my-app",
"os": "ubuntu",
"version": "24.04",
"storage": "tiny",
"compute": "small",
"username": "ubuntu",
"privateIp": "10.20.0.5",
"hostname": "i-6a4d5b83b7006ccae6391810",
"domainName": "your-pod.microapps.io",
"privatePort": 22,
"publicPort": 3001,
"status": "Running",
"type": "instance",
"networkId": "6a4d5b83b7006ccae6391a22",
"serverId": "6a4d5b83b7006ccae639170f",
"firewallId": "6a4d5b83b7006ccae6391b07",
"accountId": "6a4d5b83b7006ccae639100a",
"createdAt": "2026-09-01T12:00:00Z",
"updatedAt": null
}
],
"total": 1,
"page": 1,
"limit": 2,
"totalPages": 1
}Create a pod
http
POST /api/podsNeeds pods:create, $3 of credit, and a free slot under your pod limit. Body (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. A name already on your account returns 409. |
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 firewall for this pod that blocks all public traffic - add your IPs to its allow-list to reach the pod. It counts against your firewall limit. |
networkId | no | Attach a private network (must be in the same Region as the pod, or you get 400 "Network must be on the same server as the pod"). Blank reuses your oldest existing network in that Region; a new private network is created only if you have none there (it then counts against your network limit). |
bash
curl -X POST https://cloud-api.microapps.io/api/pods \
-H "Authorization: Bearer $MICROAPPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"os": "ubuntu",
"osVersion": "24.04",
"compute": "small",
"storage": "tiny",
"serverId": "6a4d5b83b7006ccae639170f"
}'Response 201:
json
{
"pod": {
"id": "6a4d5b83b7006ccae6391810",
"name": "my-app",
"os": "ubuntu",
"version": "24.04",
"storage": "tiny",
"compute": "small",
"username": "ubuntu",
"privateIp": "",
"hostname": "i-6a4d5b83b7006ccae6391810",
"domainName": "your-pod.microapps.io",
"privatePort": 22,
"publicPort": 3001,
"status": "Initializing",
"type": "instance",
"networkId": "6a4d5b83b7006ccae6391a22",
"serverId": "6a4d5b83b7006ccae639170f",
"firewallId": "6a4d5b83b7006ccae6391b07",
"accountId": "6a4d5b83b7006ccae639100a",
"createdAt": "2026-09-01T12:00:00Z",
"updatedAt": null
},
"password": "kQ8rTm2xLb9vHs4wZe6yPa1n"
}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, with privateIp still empty, and finishes provisioning in the background.
Describe a pod
http
GET /api/pods/:idNeeds pods:read. Returns the pod object. 404 if it isn't yours.
bash
curl https://cloud-api.microapps.io/api/pods/6a4d5b83b7006ccae6391810 \
-H "Authorization: Bearer $MICROAPPS_API_KEY"Start, stop, restart
http
GET /api/pods/:id/action/:actionNeeds pods:update. :action is one of start, stop, restart; anything else is 400 "Invalid action". Response 204 with no body.
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/6a4d5b83b7006ccae6391810/action/restart \
-H "Authorization: Bearer $MICROAPPS_API_KEY"The recorded status is reconciled against the pod's real state afterwards, so a restart on an already-stopped pod leaves it Stopped rather than claiming it is running.
Update a pod (rename, resize, reassign)
http
PUT /api/pods/:idNeeds pods:update. Body (UpdatePodItem), all optional:
| Field | Effect |
|---|---|
name | Rename. Applies immediately. A name already on your account returns 409. |
compute | Change compute size. Triggers an async Resize (the pod stops and restarts). |
storage | Change disk size. Async Resize. |
firewallId | Swap the firewall. Applies without a resize. |
networkId | Move to another private network in the same Region. Async Resize, and the pod gets a new privateIp. |
bash
curl -X PUT https://cloud-api.microapps.io/api/pods/6a4d5b83b7006ccae6391810 \
-H "Authorization: Bearer $MICROAPPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "compute": "medium" }'Response 200 with the whole pod object: the fields you sent already carry their new values, and status has already flipped to Resizing. The resize itself runs in the background - poll describe until the status settles.
Notes:
- Storage resizes in either direction. A smaller
storagevalue is accepted. The data has to fit in the new size, so check the pod's disk usage before shrinking it. - A resize leaves the pod running. It stops the pod, applies the change, and starts it again, so a pod that was
Stoppedbefore the resize comes backRunning. - A rename with no size change returns
200and the pod stays as it was - no resize is queued. - 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. - If the resize itself fails, the pod lands in
ResizeFailed, notRunning.
Delete a pod
http
DELETE /api/pods/:idNeeds pods:delete. Response 204. The pod flips to Deleting and 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-passwordNeeds pods:update. Database pods only. Generates a new database password, applies it on the pod, and returns it. Response 200:
json
{ "password": "Xb7pQz2mLr4tKv9sNc3h" }bash
curl -X POST https://cloud-api.microapps.io/api/pods/6a4d5b83b7006ccae6391810/reset-password \
-H "Authorization: Bearer $MICROAPPS_API_KEY"This is the only endpoint that hands back a pod credential after creation, and the new password is shown once here too. On an instance pod it returns 400 ("Pod is not a database type") - instance pods have no resettable login password, see Connecting via SSH.
Live telemetry
http
GET /api/pods/:id/telemetryNeeds pods:read. 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: ..."). cpuUsagePercent is 0 until there are two samples to compare, so a pod that has just started reports zero CPU for one interval.
Telemetry history
http
GET /api/pods/:id/telemetry/history?from=2026-09-01&to=2026-09-08Needs pods:read. from and to accept RFC3339 (2026-09-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 - an older from is silently pulled forward. Response is an array of hourly points:
bash
curl "https://cloud-api.microapps.io/api/pods/6a4d5b83b7006ccae6391810/telemetry/history?from=2026-09-08&to=2026-09-08" \
-H "Authorization: Bearer $MICROAPPS_API_KEY"json
[
{
"timestamp": "2026-09-08T13:00:00Z",
"cpuUsagePercent": 5.4,
"memoryUsagePercent": 32.8,
"diskUsagePercent": 12.4,
"networkInBytes": 917504,
"networkOutBytes": 229376
},
{
"timestamp": "2026-09-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-ticketNeeds pods:console. Returns 200 with a single-use ticket, good for 30 seconds:
json
{ "ticket": "5f2c9b41-7ad3-4e28-9c16-0b8e5d3a7f92" }Then open a WebSocket with the ticket:
wss://cloud-api.microapps.io/api/pods/:id/console?ticket=<ticket>The ticket is consumed on connect, so each session needs a fresh one. An idle session is closed after one hour. A ticket that has expired or has already been used gets 401 "Invalid or expired 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 name is already taken, or the pod is busy with another operation. |
See also
- Pods concept
- Snapshots API and Backups API - backups also own clone a pod
- Quickstart: deploy a pod