API and Automation
The interface uses the same HTTP API that is open to automation. The base
path is /api/v1.
Authentication
Section titled “Authentication”Two ways in:
-
Session cookie — the interface’s way (
POST /api/v1/auth/login). -
API key as bearer token — the way for scripts and CI:
Terminal window curl -H "Authorization: Bearer dnse_…" https://dns.example.com/api/v1/zones
API keys
Section titled “API keys”| Kind | Reach |
|---|---|
| Project key | one project, with a project role (e.g. Editor) |
| Admin key | instance-wide, limited by scopes |
Admin scopes are users, teams, projects, and settings. An admin key
without projects cannot access any project data.
Keys are shown once at creation; only a hash is stored. An expiry date is possible — 14 days beforehand, an email reminds the administrators, because keys do not renew themselves.
Creating: Project → Management → API Keys (project key) or Administration → API Keys (admin key).
Walkthrough: from empty system to managed zone
Section titled “Walkthrough: from empty system to managed zone”The complete path via curl — with a project key holding the Editor role:
H='Authorization: Bearer dnse_YourKeyHere'BASE=https://dns.example.com/api/v11. Register a provider account (Hetzner example, a single API token):
curl -s -X POST "$BASE/accounts/register" -H "$H" \ -H "Content-Type: application/json" \ -d '{ "projectId": "01…", "provider": "hetzner", "name": "Hetzner Produktion", "credentials": { "api_token": "…" } }'# → 201 {"id":"01…","status":"ok",…}Which credential fields a provider expects is told by
GET /api/v1/providers — every integration describes its own fields.
2. View the account’s zones (onboarding list):
curl -s "$BASE/accounts/<accountId>/zones" -H "$H"# → [{"name":"example.com"},{"name":"example.org"}]3. Adopt a zone:
curl -s -X POST "$BASE/zones/adopt" -H "$H" \ -H "Content-Type: application/json" \ -d '{"accountId": "<accountId>", "zone": "example.com"}'# → 201 {"id":"<zoneId>",…}4. Write a record set — replaces the whole set identified by name and type:
curl -s -X POST "$BASE/zones/<zoneId>/record-sets/upsert" -H "$H" \ -H "Content-Type: application/json" \ -d '{"name": "www", "type": "A", "ttl": 300, "records": ["203.0.113.10", "203.0.113.11"]}'5. Check and resolve drift:
curl -s "$BASE/zones/<zoneId>/drift" -H "$H"# → [{"id":"<driftId>","kind":"MODIFIED","name":"www","type":"A",…}]
curl -s -X POST "$BASE/drift/<driftId>/resolve" -H "$H" \ -H "Content-Type: application/json" \ -d '{"action": "enforce"}' # or "adopt" / "ignore"6. Search across providers:
curl -s "$BASE/inventory/search?q=203.0.113.10&type=A" -H "$H"# → {"hits":[…],"truncated":false}Other common calls
Section titled “Other common calls”| Purpose | Call |
|---|---|
| Instance info (public, no auth) | GET /api/v1/instance |
| Request a certificate | POST /api/v1/projects/{id}/certificates/request |
| Download a certificate | GET /api/v1/certificates/{id}/download |
| Start a provider migration | POST /api/v1/zones/{id}/migrate |
| Migrations of a project | GET /api/v1/projects/{id}/migrations |
| DynDNS update (router) | GET /nic/update (Basic Auth with token) |
| DynDNS update (script) | POST /api/v1/dyndns/update (bearer) |
The complete list with all parameters and schemas is in the generated API
reference (sidebar → DNS-Editor API). Alongside the specification
maintained in the repo (api/openapi.yaml), every instance serves the
runtime-generated version at /api/v1/openapi.json.
Error behavior
Section titled “Error behavior”Errors come as JSON with an error field and a matching status:
| Status | Meaning |
|---|---|
400 | invalid input |
401 | no valid authentication |
403 | role or scope insufficient |
404 | does not exist or lies outside your reach |
409 | conflict (e.g. zone already managed) |
That “foreign” and “nonexistent” look the same is intentional: the API does not reveal what exists beyond your own permissions.