Appearance
Firewalls API
All endpoints require a bearer token (a login JWT or an API key) and a permission, listed with each endpoint.
A firewall is a plain IP allow-list. Each rule is an IP or CIDR plus a description - that is the whole model. There are no ports, protocols, or directions. Only source addresses on the list can reach the pod; everyone else is blocked. Firewalls attach to a pod from the pod side (at pod creation, or via firewallId on update a pod).
The firewall object
json
{
"id": "6a4d5b83b7006ccae6391b07",
"name": "web-only",
"rules": [
{ "cidr": "203.0.113.42", "description": "Home IP" },
{ "cidr": "198.51.100.0/24", "description": "Office range" }
],
"accountId": "6a4d5b83b7006ccae639100a",
"createdAt": "2026-09-01T12:00:00Z",
"updatedAt": "2026-09-14T10:02:00Z"
}A rule has exactly two fields:
| Field | Type | Notes |
|---|---|---|
cidr | string | An IP (203.0.113.42) or CIDR (198.51.100.0/24). A bare IP is stored exactly as you sent it - we do not rewrite it to /32. |
description | string | Free text, trimmed and truncated to 255 characters. |
List firewalls
http
GET /api/firewalls?page=1&limit=20&search=webNeeds firewalls:read. Paginated, newest first; search matches name.
bash
curl https://cloud-api.microapps.io/api/firewalls \
-H "Authorization: Bearer $MICROAPPS_API_KEY"Create a firewall
http
POST /api/firewallsNeeds firewalls:create, $3 of credit, and a free slot under your firewall limit.
bash
curl -X POST https://cloud-api.microapps.io/api/firewalls \
-H "Authorization: Bearer $MICROAPPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "web-only",
"rules": [
{ "cidr": "203.0.113.42", "description": "Home IP" },
{ "cidr": "198.51.100.0/24", "description": "Office range" }
]
}'Response 201 with the firewall object. Rules follow a few rules of their own:
- At least one rule is required (
400 "At least one firewall rule is required"). - At most 15 rules per firewall (
400 "A maximum of 15 firewall rules are allowed per firewall"), counted after duplicates are dropped. - Duplicate CIDRs are silently dropped rather than rejected.
- An address that is neither a valid IP nor a valid CIDR is
400 "Invalid IP address: ..."or400 "Invalid CIDR notation: ...". 0.0.0.0/0(allow everyone) is stored as0.0.0.0.nameis optional (we generate one if blank), 2 to 30 characters, and unique within your account - a clash is409 "Firewall name ... is already taken".
Firewalls themselves are free, but the create call still needs at least $3 of account credit like any other create (402 otherwise).
Describe a firewall
http
GET /api/firewalls/:idNeeds firewalls:read. Returns the firewall object.
Firewall stats
http
GET /api/firewalls/:id/statsNeeds firewalls:read.
json
{
"blocked": 1840,
"allowed": 502100,
"activeSessions": 12,
"bytesIn": 44210000,
"bytesOut": 981200000,
"portCount": 3
}blocked is the number of connections the allow-list rejected. portCount is how many public ports the firewall currently guards (one per pod service it fronts) - it does not mean rules have ports. Values are cumulative.
Update a firewall
http
PUT /api/firewalls/:idNeeds firewalls:update.
bash
curl -X PUT https://cloud-api.microapps.io/api/firewalls/6a4d5b83b7006ccae6391b07 \
-H "Authorization: Bearer $MICROAPPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{ "cidr": "203.0.113.42", "description": "Home IP" },
{ "cidr": "198.51.100.0/24", "description": "Office range" },
{ "cidr": "192.0.2.17", "description": "CI runner" }
]
}'The rules array replaces the existing rules wholesale, so send the full list you want, not just the new entry. It only replaces when you send a non-empty array:
| You send | Result |
|---|---|
{"rules": [ ... ]} | The existing rules are replaced by these. |
{"name": "new-name"} | Rename only; rules untouched. |
{"rules": []} | Nothing happens to the rules. This is not how you clear an allow-list - a firewall with no rules cannot exist. |
Response 200 with the updated firewall. Changes take effect within seconds; the pod does not restart.
Delete a firewall
http
DELETE /api/firewalls/:idNeeds firewalls:delete. Returns 204. If a pod is still using the firewall it returns 400 "Firewall is in use by existing pods". Every pod has a firewall at all times, so point those pods at a different one first (firewallId on update a pod) rather than expecting to detach this one.