Skip to content

Domains and DNS API

A domain choice is {"type": "generated" | "test" | "custom", "name"?: string}; a plain string is a custom domain. It is accepted by POST /api/v1/sites (domain), Compose public_services[].domain, and template deploys (domains.<service>). A service without a choice gets the organization default: the test domain when FALAK_TEST_DOMAIN is set, else a generated name, else a domain is required.

Type Result
generated <label>.<ipv4-with-dashes>.<suffix>, e.g. minio-files.63-182-218-247.sslip.io. Label: the site slug (Compose: <service>-<slug>). IP: the leader’s public IPv4, or the load balancer’s. 422 when generated names are off or the server has no public IPv4 yet.
test <slug>.<FALAK_TEST_DOMAIN> (Compose: <service>-<slug>.… after the first service)
custom Your domain, with automatic TLS once DNS points at the server

What a create form offers for a set of servers, or for an existing site.

GET /api/v1/domains/options?server=01k…,01k…
GET /api/v1/domains/options?server[]=01k…&server[]=01k…
GET /api/v1/domains/options?site=shop
200 OK
{"data": {
"test_domain": null,
"generated": {"suffix": "sslip.io", "ipv4": "63.182.218.247", "target": "app-1", "available": true, "reason": null},
"default": "generated",
"targets": [{"server_id": "01k…", "name": "app-1", "ipv4": "63.182.218.247", "ipv6": null, "load_balancer": false}]
}}

targets lists where DNS must point: the site’s load balancer, else each server, leader first.

Resolves a name from the control plane and compares it with the targets. Rate limited to 60/minute.

GET /api/v1/dns/check?name=shop.example.com&server=01k…
GET /api/v1/dns/check?name=shop.example.com&site=shop&tls=1

The name is resolved over DNS-over-HTTPS (FALAK_DNS_RESOLVER=doh, default resolver https://cloudflare-dns.com/dns-query) or the system resolver (system), with a 3-second timeout.

200 OK
{"data": {
"name": "shop.example.com", "status": "ok", "message": "Points to app-2 (63.182.218.247)",
"addresses": ["63.182.218.247"], "cnames": [], "targets": [], "matched": [],
"instructions": {"zone": "example.com", "host": "shop", "apex": false, "ttl": 300,
"records": [{"type": "A", "name": "shop.example.com", "host": "shop", "value": "63.182.218.247", "target": "app-2"}],
"alternative": {"type": "CNAME", "host": "shop", "value": "shop.63-182-218-247.sslip.io"}, "notes": ["…"]},
"certificate": null, "checked_at": "2026-09-28T12:00:00+00:00"}}
status Meaning
ok Every address is a target
mismatch Resolves elsewhere (“Resolves to 1.2.3.4 — expected …”), or has extra records to remove
proxied Cloudflare proxy addresses; HTTP-01 fails until the record is “DNS only”
missing No A/AAAA record yet
error Lookup failed, invalid name, or no server IP to compare with

instructions lists the records to add: an A per target IPv4, an AAAA per IPv6, apex vs subdomain, and for subdomains of single-target sites a CNAME to the generated name as an alternative.

With site and tls=1, certificate reports what the site’s server serves for the name — {status: issued|pending, issuer, expires_at, message} — probed only when the name already points at the site.

GET (edge.view), PUT and DELETE (edge.manage) on /api/v1/sites/{site}/domains/{domain}/rate-limit: a domain’s Cloudflare rate limit. {domain} is the domain’s id or name. Rate limited to 30/minute. See Cloudflare → Rate limits.

GET → 200 OK
{"data": {
"domain": "shop.example.com", "rule": {"path": "/login", "requests": 20, "period": 10, "action": "block", "timeout": 10},
"zone": "example.com", "proxied": true,
"limits": {"plan": "free", "rules": 1, "host": false, "periods": [10], "timeouts": [10], "challenge_timeout": false, "note": "…"},
"zone_rule": null}}
  • limits is null outside a managed zone. zone_rule ({domain, path}) is another domain’s Free-plan rule that applies to this domain too.
  • PUT {path?, requests, period, action: block|managed_challenge, timeout} writes the zone’s rules and returns the same shape. For managed_challenge with challenge_timeout: false (below Enterprise), timeout is ignored and stored as 0.
  • 422 when the domain isn’t proxied, the plan doesn’t allow the window or duration or has no rule left, or Cloudflare refuses (the token needs Zone → Zone WAF → Edit).
  • DELETE removes the rule.