Skip to content

Domains API ​

All endpoints require a bearer token (a login JWT or an API key) and a permission, listed with each endpoint.

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": "6a4d5b83b7006ccae6391c41",
  "name": "example.com",
  "type": "fqdn",
  "status": "Mapped",
  "hasEncryption": true,
  "ownershipStatus": "verified",
  "ownershipLocked": true,
  "podId": "6a4d5b83b7006ccae6391810",
  "accountId": "6a4d5b83b7006ccae639100a",
  "emailServerId": null,
  "delegationLabel": "3f9c1a7d2e6b48059c1d7e2a4b60f835",
  "mapping": {
    "trafficType": "https",
    "internalPort": 3000,
    "internalIp": "10.20.0.5"
  },
  "validationData": { "name": "", "type": "", "ttl": 0, "value": "" },
  "dnsStatus": "configured",
  "dnsCheckedAt": "2026-09-16T14:15:00Z",
  "dnsIssue": "",
  "dnsIssueDetail": "",
  "dnsRecords": [
    {
      "type": "TXT",
      "recordType": "TXT",
      "name": "microapps-verify",
      "hostname": "microapps-verify.example.com",
      "value": "8f2c1d0a-6a41-4c9e-9a77-3f2b8e5d1c04",
      "status": "verified",
      "ttl": 300,
      "category": "ownership",
      "purpose": "Domain Ownership Verification",
      "description": "TXT record proving you control this domain. Publish the value exactly as shown; it verifies ownership and locks the domain to your account.",
      "required": true
    }
  ],
  "uptimeStatus": "healthy",
  "uptimeCode": 200,
  "uptimeCheckedAt": "2026-09-16T14:30:00Z",
  "createdAt": "2026-09-01T12:00:00Z",
  "updatedAt": "2026-09-01T12:05:00Z"
}
FieldMeaning
nameThe hostname.
typeDerived from the name, not something you set: fqdn (a root domain like example.com), subdomain (app.example.com), or local. It is unknown for a moment right after you add the domain, while classification runs.
statusUnmapped, Configuring, Mapped, or ConfigFailed. Empty on a domain that has never been mapped.
hasEncryptiontrue once SSL is active.
ownershipStatusowned, pending, verified, or unverified.
ownershipLockedtrue once you have proven ownership. Latches on, and is what gates email on the domain.
podIdThe mapped pod, or null when unmapped.
mappingThe current mapping: trafficType (http/https), internalPort, internalIp. Not null when unmapped - every field is just zeroed. Check podId instead.
delegationLabelYour domain's stable token in the _acme-challenge CNAME target.
dnsRecordsEvery record we expect on the domain. See below.
dnsStatusRoll-up: unknown, incomplete, operational (every required record verified, some optional ones missing), configured (everything verified), or misconfigured (a required record that used to work has stopped).
dnsIssue / dnsIssueDetailSee When DNS is broken at the registrar.
uptimeStatushealthy, degraded, down, or empty if not checked yet. uptimeCode carries the last HTTP status seen. History is on the Monitoring API.

Each entry in dnsRecords looks like the example above:

FieldMeaning
typeThe record's role: TXT, MX, SRV, CNAME, ALIAS, plus DKIM, SPF and DMARC for the email records.
recordTypeThe DNS record type you actually publish, so DKIM, SPF and DMARC all show TXT. Use this one at your DNS provider.
nameThe host label to create, relative to the zone (@ means the domain itself).
hostnameThe same thing spelled out as a full name, for checking with dig.
statusverified, unverified (looked up, not there or wrong), or unknown (the lookup itself failed).
requiredWhether a missing value counts against dnsStatus. MX, SPF, DKIM, the ownership TXT and the pointing record are required; DMARC, the SRV records and the _acme-challenge CNAME are not.
ttlAlways 300.

List domains ​

http
GET /api/domains?page=1&limit=20&search=example

Needs domains:read. Paginated, newest first; search matches name, type, and status. Domains you have an open claim on are listed first, with their validationData filled in.

bash
curl "https://cloud-api.microapps.io/api/domains?limit=20" \
  -H "Authorization: Bearer $MICROAPPS_API_KEY"
json
{
  "data": [ { "id": "6a4d5b83b7006ccae6391c41", "name": "example.com", "type": "fqdn", "status": "Mapped" } ],
  "total": 1,
  "page": 1,
  "limit": 20,
  "totalPages": 1
}

Add a domain ​

http
POST /api/domains

Needs domains:create, $3 of credit, and a free slot under your domain limit.

bash
curl -X POST https://cloud-api.microapps.io/api/domains \
  -H "Authorization: Bearer $MICROAPPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "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:

StatusWhenWhat it means
201 CreatedNobody has this domain yetAdded. The response already carries the microapps-verify TXT record to publish.
200 OKYou already own itReturns your existing domain, unchanged.
428 Precondition RequiredSomeone else added it but never verifiedYou can claim it. Publish the microapps-verify TXT record within 48 hours to take it over. Body: {"message":"Requires validation"}.
409 ConflictVerified and locked by another accountIt cannot be claimed. Body: {"message":"This domain is already verified by another account and cannot be claimed."}.

To prove ownership, publish a TXT record at microapps-verify.example.com with the value from that record in dnsRecords. Verification is rechecked on a schedule, or on demand with revalidate. 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/:id

Needs domains:read. Returns the full domain object, including the current dnsRecords set and their verification status.

bash
curl https://cloud-api.microapps.io/api/domains/6a4d5b83b7006ccae6391c41 \
  -H "Authorization: Bearer $MICROAPPS_API_KEY"

Domain stats ​

http
GET /api/domains/:id/stats

Needs domains:read. Traffic and response stats measured at the domain's public entry point:

json
{
  "requests": 128400,
  "status2xx": 121000,
  "status3xx": 2100,
  "status4xx": 5100,
  "status5xx": 200,
  "bytesIn": 44210000,
  "bytesOut": 981200000,
  "avgResponseMs": 42
}

Every value is zero when the domain has no pod mapped.

Map to a pod ​

http
POST /api/domains/:id/map

Needs domains:update.

FieldTypeNotes
trafficTypestringhttp or https, case-insensitive. Anything else is 400 "Traffic type must be either 'http' or 'https'".
portnumberRequired. The internal port your app listens on. Omitting it is 400 "Port is required (the port your app listens on inside the pod)".
podIdstringThe pod to forward to. Must be one of yours.
bash
curl -X POST https://cloud-api.microapps.io/api/domains/6a4d5b83b7006ccae6391c41/map \
  -H "Authorization: Bearer $MICROAPPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "trafficType": "https", "port": 3000, "podId": "6a4d5b83b7006ccae6391810" }'

Response is 201 with no body; the domain moves to Configuring, then Mapped (or ConfigFailed). Poll describe to see which. You cannot map to a database pod (400 "Cannot map domain to a database pod").

Mapping adds the pointing record (category web-ingress) to dnsRecords. It always points your domain at a hostname, never at an IP address:

Domain typeRecordNameValue
subdomainCNAME@the target hostname in the record's value
fqdn (root domain)ALIAS@the target hostname in the record's value

A plain CNAME cannot sit at the root of a zone next to your MX, NS and TXT records, which is why a root domain gets an ALIAS - your provider may call it ANAME or CNAME flattening. If yours offers none of those, see root domains and ALIAS records.

Unmap ​

http
DELETE /api/domains/:id/map

Needs domains:update. Response 204. Stops forwarding, zeroes the mapping, clears podId and uptimeStatus, and drops the pointing record from dnsRecords (the next map re-adds it, aimed at the new pod's Region). Status becomes Unmapped. Returns 400 "Domain is not mapped to a pod" if it wasn't mapped.

The SSL certificate is kept, so re-mapping later does not mean re-issuing it.

Enable SSL ​

http
POST /api/domains/:id/ssl

Needs domains:ssl. Response 201 with no body; issuance runs in the background and hasEncryption flips to true when it lands.

bash
curl -X POST https://cloud-api.microapps.io/api/domains/6a4d5b83b7006ccae6391c41/ssl \
  -H "Authorization: Bearer $MICROAPPS_API_KEY"

Provisions a certificate covering the domain and its subdomains, then auto-renews it for as long as the domain exists. There is no mapping prerequisite - an unmapped domain gets its certificate now and has it installed when you map it. What is required is the domain's ssl record (the _acme-challenge CNAME in dnsRecords): issuance and every renewal run through it, so leave it published.

INFO

Enabling SSL is a sensitive action - a team member needs the domains:ssl permission to call it. Calling it on a domain that already has SSL is a no-op that still returns 201. There is no disable-SSL endpoint; unmapping keeps the certificate.

When DNS is broken at the registrar ​

If the domain's registry DS record no longer matches the keys its DNS host serves, validating resolvers refuse every record under the domain. We cannot verify your records and visitors cannot reach you. The domain carries it on two fields:

FieldValue
dnsIssue"dnssec", or "" when there is no such problem.
dnsIssueDetailThe resolver's own explanation when it gave one, e.g. "DNSKEY Missing: no SEP matching the DS found for example.com.". Often empty.

The fix is at your registrar, not here: check DNSSEC.

Delete a domain ​

http
DELETE /api/domains/:id

Needs domains:delete. Returns 204. If the domain still has mailboxes, you get 400 "Cannot delete domain with active emails" - delete the mailboxes first.

Built for the long tail.