Appearance
Domains API
All endpoints require a bearer token (a login JWT or an API key).
A domain is a hostname you connect to MicroApps and prove you own. Once verified, you can map it to a pod and turn on SSL. Domain names are globally unique across all accounts - see Add a domain for what happens when a name is already taken.
The domain object
json
{
"id": "dom_abc123",
"name": "example.com",
"type": "fqdn",
"status": "Mapped",
"hasEncryption": true,
"ownershipStatus": "owned",
"podId": "pod_xyz789",
"mapping": {
"trafficType": "https",
"internalPort": 3000,
"internalIp": "10.20.0.5"
},
"dnsStatus": "configured",
"dnsRecords": [
{
"type": "TXT",
"name": "verify-domain.example.com",
"value": "microapps-verify=8f2c...",
"status": "verified",
"ttl": 300,
"category": "ownership",
"required": true
}
],
"uptimeStatus": "healthy",
"createdAt": "2026-07-01T12:00:00Z",
"updatedAt": "2026-07-01T12:05:00Z"
}| Field | Meaning |
|---|---|
name | The hostname. |
type | Derived from the name, not something you set: fqdn (a root domain like example.com), subdomain (app.example.com), or local. |
status | Unmapped, Configuring, Mapped, ConfigFailed, Healthy, or Unhealthy. |
hasEncryption | true once SSL is active. |
ownershipStatus | owned, pending, verified, or unverified. |
podId | The mapped pod, or null when unmapped. |
mapping | The current mapping: trafficType (http/https), internalPort, internalIp. null when unmapped. |
dnsRecords | Every record we expect on the domain, each with its live status (verified / unverified / unknown) and whether it is required. |
dnsStatus | Roll-up: unknown, incomplete, operational, configured, or misconfigured. |
uptimeStatus | healthy, degraded, down, or empty if not mapped. |
List domains
http
GET /api/domains?page=1&limit=20Returns a paginated list of your domains.
Add a domain
http
POST /api/domainsjson
{ "name": "example.com" }The name is the only input. The type is worked out from the name and returned in the response - you never choose it.
Because domain names are globally unique, one request has four possible outcomes:
| Status | When | What it means |
|---|---|---|
201 Created | Nobody has this domain yet | Added. Publish the verify-domain TXT record we show you to prove ownership. |
200 OK | You already own it | Returns your existing domain, unchanged. |
428 Precondition Required | Someone else added it but never verified | You can claim it. Publish the verify-domain TXT record within 48 hours to take it over. Body: {"message":"Requires validation"}. |
409 Conflict | Verified and locked by another account | It cannot be claimed. Body: {"message":"This domain is already verified by another account and cannot be claimed."}. |
To verify ownership, publish a TXT record at verify-domain.<your-domain> with the value from the domain's dnsRecords. If a claim is not completed within 48 hours it is dropped.
Credit gate
Like every create action, this needs at least $3 of available credit on your account, or it returns 402.
Describe a domain
http
GET /api/domains/:idReturns the full domain object, including the current dnsRecords set and their verification status.
Domain stats
http
GET /api/domains/:id/statsTraffic and response stats for a mapped domain:
json
{
"requests": 128400,
"status2xx": 121000,
"status3xx": 2100,
"status4xx": 5100,
"status5xx": 200,
"bytesIn": 44210000,
"bytesOut": 981200000,
"avgResponseMs": 42
}Values are zero when the domain has no live pod mapping.
Map to a pod
http
POST /api/domains/:id/mapjson
{
"trafficType": "https",
"port": 3000,
"podId": "pod_xyz789"
}| Field | Type | Notes |
|---|---|---|
trafficType | string | http or https (lowercase). Anything else is 400. |
port | number | The internal port your app listens on. Always send it - there is no default. |
podId | string | The pod to forward to. Must be one of yours. |
Traffic for the domain is forwarded to the pod on the given port. You cannot map to a database pod (400 "Cannot map domain to a database pod"). This runs asynchronously: the call returns 201 and the domain moves to Configuring, then Mapped.
Unmap
http
DELETE /api/domains/:id/mapStops forwarding and clears the mapping (status becomes Unmapped). The SSL certificate is kept, so re-mapping later does not mean re-issuing it. Returns 400 if the domain was not mapped.
Enable SSL
http
POST /api/domains/:id/sslProvisions an SSL certificate covering the domain and its subdomains, then auto-renews it for as long as the domain exists. There is no mapping prerequisite, but the domain's ssl DNS record (the _acme-challenge CNAME in dnsRecords) must be published at your DNS provider - issuance and renewal both run through it. The call returns 201 and issuance runs asynchronously.
INFO
Enabling SSL is a sensitive action - a team member needs the domains:ssl permission to call it. There is no disable-SSL endpoint; unmapping keeps the certificate.
Delete a domain
http
DELETE /api/domains/:idReturns 204. If the domain still has email accounts, you get 400 ("Cannot delete domain with active emails") - delete the mailboxes first.
curl examples
Add a domain:
bash
curl -X POST https://cloud-api.microapps.io/api/domains \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "example.com" }'Map it to a pod over HTTPS on internal port 3000:
bash
curl -X POST https://cloud-api.microapps.io/api/domains/dom_abc123/map \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "trafficType": "https", "port": 3000, "podId": "pod_xyz789" }'Enable SSL:
bash
curl -X POST https://cloud-api.microapps.io/api/domains/dom_abc123/ssl \
-H "Authorization: Bearer $TOKEN"