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 inherits the cluster default (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.
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, and not_found. 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, newly created zones start life locked unless the create says
otherwise. It applies at creation only, never retroactively; see
deletion locks.
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.