Managing DNS with the API
This page covers the zone and record endpoints with curl examples. All
requests need a bearer token; see the API overview. Writes
require the editor or admin role.
List zones
Section titled “List zones”curl -sk https://dtm.internal:8443/api/v1/zones \ -H "Authorization: Bearer $DTM_TOKEN"Create a zone
Section titled “Create a zone”curl -sk -X POST https://dtm.internal:8443/api/v1/zones \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{ "name": "example.internal.", "default_ttl": 300, "soa": {"mname": "ns1.example.internal.", "rname": "hostmaster.example.internal."} }'soa is optional; DTM fills sensible defaults. An optional deletion_lock
(bool) sets the zone’s deletion lock
explicitly; omit it and the zone takes the default of the node that receives
the request (see
delete guardrails).
Get, update, delete a zone
Section titled “Get, update, delete a zone”curl -sk https://dtm.internal:8443/api/v1/zones/<zone-id> \ -H "Authorization: Bearer $DTM_TOKEN"
curl -sk -X PUT https://dtm.internal:8443/api/v1/zones/<zone-id> \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{"default_ttl": 600}'
curl -sk -X DELETE https://dtm.internal:8443/api/v1/zones/<zone-id> \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1"Deleting a zone removes all of its records. If the zone carries a
deletion lock, the DELETE
returns 409 and nothing is deleted; clear the lock first, then delete:
curl -sk -X PUT https://dtm.internal:8443/api/v1/zones/<zone-id> \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{"deletion_lock": false}'Records
Section titled “Records”List records in a zone
Section titled “List records in a zone”curl -sk https://dtm.internal:8443/api/v1/zones/<zone-id>/records \ -H "Authorization: Bearer $DTM_TOKEN"Create a record
Section titled “Create a record”A load-balanced, health-checked A record:
curl -sk -X POST https://dtm.internal:8443/api/v1/zones/<zone-id>/records \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{ "name": "web", "type": "A", "ttl": 60, "values": [{"value": "10.0.1.10"}, {"value": "10.0.1.11"}], "lb_policy": {"method": "round-robin"}, "health_check_template": { "protocol": "http", "port": 80, "path": "/healthz", "interval_seconds": 15, "timeout_seconds": 5 } }'Supported types: A, AAAA, CNAME, MX, NS, PTR, SRV, TXT, CAA,
ALIAS. For CAA, set tag (issue, issuewild, or iodef) and carry the
CAA flag (0 to 255) in priority; an empty value is valid and means deny
all issuance.
For A, values must be valid IPv4; for AAAA, valid IPv6. CNAME cannot
coexist with other types at the same name. The lb_policy object takes
method (round-robin, failover, latency, region, geo-failover,
weighted), client_affinity, affinity_ttl_seconds, failover_order,
region_preference, and, for weighted, a value_weights map of record
value to percentage (an unknown method, a weight outside 0 to 100, listed
weights summing past 100, or a key matching no value is rejected); see
load-balancing methods for what each does,
and authenticated health probes for probing secured backends.
ttl accepts 0 (inherit the zone default) up to 2147483647 seconds, the
RFC 2181 ceiling; a larger value is rejected with 400 on create and update.
Get, update, delete a record
Section titled “Get, update, delete a record”Records are addressed by their own ID (not the zone) for read, update, delete:
curl -sk https://dtm.internal:8443/api/v1/records/<record-id> \ -H "Authorization: Bearer $DTM_TOKEN"
curl -sk -X PUT https://dtm.internal:8443/api/v1/records/<record-id> \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{"ttl": 30, "values": [{"value": "10.0.1.10"}]}'
curl -sk -X DELETE https://dtm.internal:8443/api/v1/records/<record-id> \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1"Delete guardrails: protected records and bulk delete
Section titled “Delete guardrails: protected records and bulk delete”Mark a record "protected": true (on create or update) and a plain DELETE
returns 409; retry with ?force=true to delete it anyway (audited
distinctly). Use it on records a sync tool must never remove.
Protection covers replacement as well as deletion, since replacing a
record deletes the incumbent. Creating a colliding record at a protected
name, or renaming another record onto it with PUT, answers 409 naming
the protected record. The bulk zone import and the Azure Private DNS import
skip a protected incumbent and report it in the response instead of
overwriting. A dynamic DNS update
that would delete a protected record is refused (added in v1.0.7); it has no
force option.
Bulk deletion has its own endpoint and cap:
curl -sk -X POST https://dtm.internal:8443/api/v1/zones/<zone-id>/records/bulk-delete \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ -d '{"ids": ["rec-...", "rec-..."], "force": false}'The response reports deleted, protected_skipped (protected records left in
place because force was false), not_found, and forced (the request’s
force, echoed). A batch
larger than the cap (default 100) is refused whole with 422 and nothing is
deleted; the cap is adjustable by an admin via
PUT /api/v1/config/delete-guardrails (bulk_delete_cap). Note the two
force spellings: bulk delete takes "force" in the JSON body, single delete
takes ?force=true as a query parameter.
The same delete-guardrails setting carries default_zone_deletion_lock: when
true, zones created through that node start life locked unless the create
says otherwise. It applies at creation only, never retroactively; see
deletion locks. Both the cap and
this default are per node: they do not replicate, so PUT them against every
node.
Import and export
Section titled “Import and export”DTM can bulk import and export zones, which is how you migrate existing DNS in.
Export
Section titled “Export”# One zone as an RFC 1035 zone filecurl -sk https://dtm.internal:8443/api/v1/zones/<zone-id>/export \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1"
# Every zone as a JSON bundle (preserves LB policy, health-check templates,# region tags)curl -sk https://dtm.internal:8443/api/v1/zones/export \ -H "Authorization: Bearer $DTM_TOKEN" -H "X-DTM-CSRF: 1" -o zones.jsonImport
Section titled “Import”The Content-Type selects the parser: application/json for a DTM JSON bundle
(preserves everything except health-check template secrets, see below),
text/csv for a spreadsheet (lossy, onboarding only), multipart/form-data or
text/dns for BIND zone files.
# Re-import the JSON bundlecurl -sk -X POST https://dtm.internal:8443/api/v1/zones/import \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ --data-binary @zones.json
# Preview without writing anythingcurl -sk -X POST "https://dtm.internal:8443/api/v1/zones/import?dry_run=true" \ -H "Authorization: Bearer $DTM_TOKEN" \ -H "X-DTM-CSRF: 1" \ -H "Content-Type: application/json" \ --data-binary @zones.jsonHealth-check template secrets (headers values, oauth2.client_secret,
mtls.key_pem) are masked as *** in the export and are not restored by
the import. Re-apply them after importing, or authenticated probes will fail and
their backends will drop out of answers.
Limits: 20 MB per request, 50 zones per request, 50,000 records per zone (the
per-zone endpoint /api/v1/zones/<zone-id>/import takes up to 10 MB). An oversized body always
fails with 413 and nothing is written. For JSON and CSV bundles, a request
over the zone or record cap fails whole with 400 before any write. For BIND
zone files, an over-cap or malformed file is instead reported per file in the
response’s parse_errors (HTTP 200) while the other files in the same
request still import, so scripts importing multiple BIND files should check
parse_errors rather than relying on the status code. The per-zone endpoint
truncates a BIND import at the record cap and notes it in errors[].
Imports are validated like individual creates. Zone and record names must be
syntactically valid DNS names, every record must carry at least one value, and
a BIND zone file must carry a complete,
fully qualified SOA (a primary nameserver and a responsible mailbox). SOA
timers and serial you omit from a JSON bundle are filled with the same
defaults as a zone create rather than imported as zeros. Malformed
input is rejected with 400 and an error naming the offending zone, row, or
record. The dry-run plan runs the same validation as the commit, including
CNAME exclusivity at a name, so a clean plan means a clean import. See
migrating to Hayami DTM for source-by-source
instructions.
ALIAS and override records
Section titled “ALIAS and override records”ALIAS records are created like any record with "type": "ALIAS" and hostname
values; see ALIAS records. Override records use a separate
/api/v1/overrides endpoint; see override records.