Errors and edge cases
HTTP status codes, monitor states, idempotency, and rate limits.
Every API error returns a JSON body of the shape:
{
"error": "<machine-readable-code>",
"message": "<human-readable explanation>",
"details": { ... }
}
details is sometimes present — for example, a list of preflight warnings
when domain creation fails preflight.
HTTP status reference
| Status | When |
|---|---|
200 | Success |
201 | Resource created |
400 | bad_request — your input is invalid (bad hostname, bad URL) |
400 | preflight_failed — domain create blocked by CAA / SSRF |
401 | unauthorized — missing or invalid Bearer token |
402 | billing_required — free quota exceeded, no payment method on file |
403 | forbidden — token valid, but action not allowed (workspace mismatch) |
404 | not_found — the resource id doesn't exist (or doesn't belong to you) |
409 | conflict — hostname already in use |
429 | rate_limited — too many requests; check Retry-After header |
500 | internal_error — our problem; report it |
Monitor states
The monitorStatus field on a Domain reflects runtime health, computed from
DNS + cert + origin reachability signals every ~60 seconds. The accompanying
monitorMessage gives a customer-facing explanation.
monitorStatus | What it means | What to show your customer |
|---|---|---|
unknown | We haven't probed yet | "Setup in progress" |
dns_not_resolving | The hostname doesn't resolve at all | "Add the CNAME record at your registrar" |
dns_incorrect | DNS resolves but not to our edge | "Update your CNAME — it points elsewhere" |
pending_ssl | DNS is correct, waiting for cert | "Almost there — SSL is being issued" |
active_ssl | Verified and serving traffic | "Your site is live" (✅) |
target_not_loading | (Reserved for future origin health checks) | — |
ssl_failed | Cert issuance failed irrecoverably | "We couldn't issue an SSL cert. Check CAA records." |
ssl_expired | The cert window elapsed without renewal | "Your domain's certificate has expired. Re-verify DNS." |
Idempotency
For idempotent retries on POST requests, send an Idempotency-Key header.
Same key + same path + same workspace within 24 hours returns the cached
original response, with header idempotent-replay: true.
curl -X POST https://api.domainee.dev/v1/domains \
-H "Authorization: Bearer $DOMAINEE_API_KEY" \
-H "Idempotency-Key: order-123-domain-create" \
-H "content-type: application/json" \
-d '{ "hostname": "shop.acme.com", "originUrl": "https://acme.fly.dev" }'
The key is scoped to your workspace — different workspaces with the same key don't collide.
Rate limits
Default: 60 requests per minute per API key across all /v1/* endpoints.
Some endpoints have additional, tighter limits:
| Endpoint | Per-key limit |
|---|---|
POST /v1/domains | 100/hour |
POST /v1/domains/bulk | 5/hour (×100 domains = 500 hostnames/hour ceiling) |
POST /v1/domains/:id/check | 60/min |
/v1/dns/check-records-* | 600/hour |
When you hit a limit you get:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1714572420
{
"error": "rate_limited",
"message": "Too many requests for action 'create_domain'. Try again in 42s.",
"details": { "action": "create_domain", "retryAfter": 42 }
}
Respect Retry-After (in seconds) to avoid hammering us during cool-down.
SSRF protection
When you submit an originUrl, we resolve its hostname and reject any value
that points at:
- Private RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Loopback (127.0.0.0/8, ::1)
- Link-local — including AWS/GCP metadata (169.254.169.254)
- CGNAT (100.64.0.0/10)
- Multicast and reserved ranges
- Hostnames like
localhost,*.local,*.internal
This protects us (and you) from being used as a tunnel to internal infrastructure.
CAA records
If a customer's domain has CAA records that don't authorize Let's Encrypt or ZeroSSL, cert issuance will fail when first traffic arrives. We surface this as a preflight warning when you create the domain:
{
"warnings": [
{
"code": "caa_blocks_lets_encrypt",
"message": "This domain has CAA records that don't authorize Let's Encrypt or ZeroSSL...",
"caaRecords": ["digicert.com"]
}
]
}
Treat this as fatal even though the domain is created — traffic will never
work until the customer fixes their CAA. The customer needs to add a CAA
record permitting letsencrypt.org or remove their existing CAA records.
When in doubt
- Check the
x-domainee-request-idresponse header on any failure — paste it in support requests so we can find the exact log line. - The dashboard's Developers page shows recent API calls per key.