Skip to content

API endpoint reference

The API endpoints, grouped by area. Base URL https://<dtm-node>:8443. All endpoints need a bearer token unless marked public. See the API overview for authentication and roles. Write operations generally require editor; cluster and user operations require admin.

Method Path Role Purpose
POST /api/v1/auth/login public Log in. Sets the JWT session cookie (valid 24h) and returns a refresh token.
POST /api/v1/auth/refresh public Exchange a refresh token for a bearer JWT. Rotates the refresh token.
POST /api/v1/auth/logout public Revoke a refresh token to end that session. Any user can end their own.
GET /api/v1/auth/entra/status public Whether Entra SSO is configured.
GET /api/v1/auth/entra/login public Start the Entra sign-in redirect.
GET /api/v1/auth/entra/callback public Entra sign-in callback (sets the session cookie).
GET /api/v1/auth/entra/config admin Read Entra config (secret redacted).
PUT /api/v1/auth/entra/config admin Update Entra config.
Method Path Role Purpose
GET /api/v1/zones read List zones.
POST /api/v1/zones editor Create a zone. Optional deletion_lock overrides the receiving node’s default.
GET /api/v1/zones/{id} read Get a zone.
PUT /api/v1/zones/{id} editor Update a zone (TTL, SOA, default lb_policy; clear_lb_policy removes the default; deletion_lock toggles the deletion lock).
DELETE /api/v1/zones/{id} editor Delete a zone and its records; 409 while the deletion lock is set.
GET /api/v1/zones/{id}/export read Export one zone (RFC 1035).
POST /api/v1/zones/{id}/import editor Import records into a zone.
GET /api/v1/zones/export read Export all zones (JSON bundle).
POST /api/v1/zones/import editor Bulk import (JSON / CSV / BIND).
GET /api/v1/zones/{zoneId}/records read List records in a zone.
POST /api/v1/zones/{zoneId}/records editor Create a record. Replaces an unprotected record of the same name and type; a protected incumbent at that name and type answers 409. Other types at the same name coexist, except that CNAME and ALIAS own a name outright. auto_populate: true creates a registration-owned record with no values.
GET /api/v1/records/{id} read Get a record.
PUT /api/v1/records/{id} editor Update a record. Renaming onto a protected name answers 409.
DELETE /api/v1/records/{id} editor Delete a record (?force=true overrides protected).
POST /api/v1/zones/{zoneId}/records/bulk-delete editor Delete many records; capped (default 100), refuses over-cap batches whole.
POST /api/v1/zones/{id}/import-azure-privatedns admin Import an Azure Private DNS zone directly over ARM (managed identity, read-only; dry_run in the body).
POST /api/v1/overrides editor Create an override record.
GET/PUT/DELETE /api/v1/overrides/{id} mixed Manage an override record.
Method Path Purpose
POST /api/v1/service-accounts Create; returns the dtmsat_ token once. Optional zone_ids confines the token’s writes to the listed zones (fail-closed; preserved across rotation).
GET /api/v1/service-accounts List (tokens redacted).
GET /api/v1/service-accounts/{id} Get one (token redacted).
POST /api/v1/service-accounts/{id}/revoke Revoke; kept in the list for audit.
POST /api/v1/service-accounts/{id}/rotate Mint a fresh secret (version-gated, so repeated calls at the same version are a no-op).
DELETE /api/v1/service-accounts/{id} Delete.

See service-account tokens.

Method Path Role Purpose
GET /api/v1/healthchecks read List health checks.
POST /api/v1/healthchecks editor Create a monitor-only check: status metrics, no DNS effect (script protocol: admin, plus the config flag).
GET/PUT/DELETE /api/v1/healthchecks/{id} mixed Manage a check (admin to set the script protocol, and since v1.0.6 to change or delete a script check at all; other writes, including DELETE, are editor).
GET /api/v1/healthchecks/{id}/status read Status of one check.
GET /api/v1/health/status read Status of all checks on this node.

Added in v1.0.5. Every route is admin-only, reads included, and answers 403 on a node whose allow_script_health_checks is off. See Managed script health checks.

Method Path Role Purpose
GET/POST /api/v1/scripts admin List scripts, or create one: version 1 when signed, otherwise this node’s draft.
GET/DELETE /api/v1/scripts/{id} admin One script; delete it with all its versions (409 while a check uses it, ?force=true to delete anyway).
POST /api/v1/scripts/{id}/versions admin Publish the next version (signed), or save this node’s draft (unsigned).
GET/DELETE /api/v1/scripts/{id}/versions/{v} admin One version, with its script text; delete it (not the latest version).
GET/DELETE /api/v1/scripts/{id}/draft admin This node’s draft, with its script text; discard it.
POST /api/v1/scripts/{id}/draft/activate admin Publish the draft, with {key_id, signature}.
POST /api/v1/scripts/{id}/test admin Run a version, or the draft, once on this node (at most once every 10 seconds per caller).
Method Path Role Purpose
GET/PUT /api/v1/forwarders/default mixed Default forwarder.
GET/PUT /api/v1/forwarders/hardening mixed Default-forwarder hardening. GET: any reader role (readonly, editor, admin); PUT: admin.
GET /api/v1/forwarders read List conditional forwarders.
POST /api/v1/forwarders editor Create a conditional forwarder.
GET/PUT/DELETE /api/v1/forwarders/{id} mixed Manage a forwarder.
GET /api/v1/stub-zones read List stub zones.
POST /api/v1/stub-zones editor Create a stub zone.
GET/PUT/DELETE /api/v1/stub-zones/{id} mixed Manage a stub zone.
GET /api/v1/blocklist read Get the blocklist.
PUT /api/v1/blocklist editor Replace the blocklist.
Method Path Role Purpose
GET /api/v1/users admin List users (hashes stripped).
POST /api/v1/users admin Create a user.
GET /api/v1/users/{id} admin Get a user.
PUT /api/v1/users/{id} admin Update role / enabled / password.
DELETE /api/v1/users/{id} admin Delete a user.
Method Path Role Purpose
GET /api/v1/nodes read List cluster members.
GET /api/v1/nodes/self read This node’s info.
DELETE /api/v1/nodes/{id} admin Remove a node from the cluster (cannot remove self); a stopped node needs ?force=true, see removing nodes.
POST /api/v1/nodes/{id}/readmit admin Readmit a removed node id so it can hold a cluster identity again, for example after a same-id rebuild. Cannot readmit self, and takes no query parameters. Returns status, node_id and readmitted_at; 404 if this node has no recorded removal for the id.
POST /api/v1/cluster/join admin Join an existing cluster at runtime.
GET /api/v1/diagnose admin Run the built-in diagnostics and return a report of findings. It also lists records the API now refuses (for example a wildcard record in a Microsoft privatelink zone).
GET /api/v1/diagnose/api-refused-records/plan admin Read-only plan of what removing those refused records would do.
POST /api/v1/diagnose/api-refused-records/remediate admin Remove the refused records you approve from a plan. Capped like bulk delete, supports dry_run, and is audited.

The API behind the UI’s Lifecycle page; see rolling upgrades for the procedure.

Method Path Role Purpose
GET /api/v1/lifecycle/oracle read Latest published image version, as seen by the version oracle. Since v1.0.6 the text of its last_error is shown to admins only; other roles read hidden (admin only).
GET/POST /api/v1/lifecycle/swap admin List stable-IP swap runs (admin since v1.0.6); start one.
GET /api/v1/lifecycle/swap/{run_id} admin Status and steps of one swap run (admin since v1.0.6).
POST /api/v1/lifecycle/swap/{run_id}/retry admin Retry a failed swap step.
POST /api/v1/lifecycle/swap/{run_id}/abort admin Abort a swap run.
GET /api/v1/lifecycle/identity admin Managed-identity permission probes for the swap.
POST /api/v1/lifecycle/restart admin Restart the DTM service on the node that receives the request (added in v1.0.7); see restart a node’s service.

Starting a swap (POST /api/v1/lifecycle/swap) takes target_node_id, target_version, stable_ip, spare_ip and, optionally, accept_outage. When the node being replaced has already failed, the request is refused with 400 and a retire_candidates list (node_id, plus last_seen_addr, status and last_seen when the cluster still knows them). Send it again with retire_node_id set to one of those ids, or with "retire_skip": true to leave the identity behind; see replacing a node that has already failed. A swap of a node that is alive needs neither field. (Changed in v1.0.4.)

Method Path Role Purpose
GET/POST/PUT/DELETE /api/v1/tsig-keys mixed Manage TSIG keys (update and delete by name).
GET/POST/DELETE /api/v1/subnet-mappings mixed Manage subnet-to-region mappings (delete by id: /api/v1/subnet-mappings/{id}). source must be manual on create; any other value is rejected.
GET /api/v1/regions read Deduplicated region IDs.
GET/POST/PUT/DELETE /api/v1/discovery/scopes admin Manage Azure discovery scopes: subscription scopes, or management-group scopes (kind: "management-group" + management_group_id + the required expanded_subscription_ids, since DTM never expands a group on its own). Reading the scope list also requires admin, like the other discovery endpoints below.
GET /api/v1/discovery/available-subscriptions admin Subscriptions the identity can see.
GET /api/v1/discovery/available-management-groups admin Management groups the identity can see. ?management_group_id=<name> expands one, separating readable from unreadable member subscriptions; add &verify=true to test each with the same subscription-wide read discovery actually makes (one ARM call per subscription, opt-in).
POST /api/v1/discovery/run admin Discover and apply subnet mappings now; returns {upserted, removed, complete}. ?dry_run=true returns the diff an apply would make (additions, removals, new_regions) and writes nothing. Removals are only proposed or applied from a complete cycle. Also applies pending management-group membership drift (additions and true removals; a still-in-group subscription the identity lost access to is never dropped) before discovering.
GET /api/v1/discovery/status admin The periodic detector’s last result and pending drift, from memory (cheap to poll). {"observed": false} until a first cycle completes. Includes membership_drift per management-group scope: added / removed (proposals) and lost_access (reported, never proposed for removal).
POST /api/v1/tag-registration/run admin Run a tag-registration cycle now; returns {updated, drained, skipped, unclaimed}. Works while the policy is disabled, and is not a dry run: it writes and drains value lists.
POST /api/v1/discovery/private-endpoints/run admin Run a Private Endpoint reconcile now; returns created/updated/reaped/unsupported counts.

PUT is admin on every row. GET is readable by the readonly, editor, and admin roles, except on /api/v1/config/rate-limits, /api/v1/config/axfr, /api/v1/config/dynamic-dns, /api/v1/config/azure-monitor, /api/v1/config/backup (since v1.0.6), /api/v1/observability/otlp, and /api/v1/config/tls-certificate, which stay admin-only for reads too: they hold credentials, describe an authentication control, or (for the certificate) report this node’s apply outcome.

Method Path Purpose
GET/PUT /api/v1/config/rate-limits Per-IP API rate limits in four classes (auth, write, read, upload; the upload class covers the disk-staging endpoints /api/v1/restore and /api/v1/admin/compact, with a deliberately severe default). GET reports the configured values, not what is in force: 0 means the built-in default applies, not that the limiter is off.
GET/PUT /api/v1/config/delete-guardrails Bulk-delete cap (bulk_delete_cap, default 100) and the default for new-zone deletion locks (default_zone_deletion_lock). Per node: not replicated, so PUT it on every node.
GET/PUT /api/v1/config/region-discovery Azure discovery backend (mode: arm-list or resource-graph).
GET/PUT /api/v1/config/axfr Zone-transfer (AXFR) settings, including outbound DNS NOTIFY (notify_targets, notify_tsig_key_name, notify_debounce_seconds); applies live, cluster-wide. See the configuration reference.
GET/PUT /api/v1/config/query-log Per-query logging (enabled, plus sample_rate to keep 1-in-N); applies live, cluster-wide.
GET/PUT /api/v1/config/dynamic-dns RFC 2136 dynamic-DNS settings.
GET/PUT /api/v1/config/cache Forwarded-response cache.
GET/PUT /api/v1/config/replication Cluster replication timing (applied on restart).
GET/PUT /api/v1/config/health-probe-policy TLS policy for authenticated probes (allow_insecure_authenticated_probes; audit-logged).
GET/PUT /api/v1/config/audit-retention Audit log age limit for the whole cluster (retention, seven days to ten years; admin; audit-logged).
GET/PUT /api/v1/config/tls-certificate The cluster’s API, DoT and DoH certificate: PUT cert_pem and a write-only key_pem once, replicated to every node and served without a restart; GET reports this node’s apply outcome (this_node) and stored_pair. See TLS certificates.
GET/PUT /api/v1/config/backup Scheduled backups (schedule_enabled, interval_minutes, retention_count, dir, blob_container_url, which must be https with no query string) and a write-only passphrase: a read returns only passphrase_set, and a PUT that leaves the field out keeps the stored one. Per node, applied at that node’s next restart.
GET/PUT /api/v1/config/azure-monitor Azure Monitor shipper settings (enabled, monitor_resource_id, monitor_region, monitor_metrics_enabled, and the query log’s Logs Ingestion target logs_ingestion_endpoint, logs_ingestion_dcr_immutable_id, logs_ingestion_stream); the record holds no secret. Changed in v1.0.6: log_analytics_workspace_id and log_analytics_workspace_key are removed, and a PUT that sends either is a 400 naming the replacement. GET also reports log_api (logs_ingestion when a target is stored, else none) and a warnings list. With enabled: true, an endpoint without a DCR immutable ID (or the reverse), a malformed ID or stream, or an endpoint that is not an https:// origin under .monitor.azure.com is a 400; see Ship the query log to Log Analytics. A blank monitor_resource_id or monitor_region means this VM, read from the Instance Metadata Service; monitor_metrics_enabled defaults to true, and an absent field in a PUT means true, so a logs-only node sends false every time. GET also reports the running exporter’s target (effective_resource_id, effective_region, target_source). Per node, applied at that node’s next restart (the PUT response says so with pending_restart and note).
GET/PUT /api/v1/config/terraform-mirror The Terraform provider mirror switch (enabled); applies live, cluster-wide. The response adds this node’s provider_dir and the provider versions it holds.
GET/PUT /api/v1/config/private-endpoints Private Endpoint automation settings.
GET/PUT /api/v1/config/tag-registration Tag-based VM registration policy (enabled, tag_key, zone_allowlist, interval_seconds); applies live, cluster-wide, except interval_seconds, which applies at each node’s next restart. PUT replaces the whole policy.
GET/PUT /api/v1/transport-config DNS-over-TLS and DNS-over-HTTPS listeners (dot_enabled, dot_listen_addr, doh_enabled, doh_listen_addr); applies live, cluster-wide. GET is readable by any authenticated role; PUT is admin.
GET/PUT /api/v1/observability/otlp OTLP metrics export.
Method Path Purpose
POST /api/v1/debug/enable Open a time-boxed debug logging window (admin).
POST /api/v1/debug/disable Close the window early (admin).
GET /api/v1/debug/status Whether the window is open (any authenticated role).
GET /api/v1/debug/tail The captured lines retained in memory on the node that answers (last 1000, with node_id, dropped and capacity); admin only. Debug capture is node-local, never replicated.
DELETE /api/v1/debug/tail Discard the answering node’s retained lines (admin).

Both /api/v1/debug/tail calls require the X-DTM-CSRF: 1 header (the GET is a sensitive read). The buffer is kept when the window closes or expires, so a capture survives the window it was taken in. See capture debug output.

Method Path Purpose
GET /api/v1/backup Stream a point-in-time snapshot, optionally passphrase-encrypted (X-DTM-Backup-Passphrase header).
POST /api/v1/restore Stage a snapshot (applied on restart); an encrypted snapshot needs the same passphrase header.
POST /api/v1/admin/compact Stage a compacted database copy.
POST /api/v1/support-bundle Build a support bundle, optionally passphrase-encrypted.
GET /api/v1/export Export the cluster’s configuration as JSON (format=json, the default) or Terraform (format=terraform), to seed another cluster. By default it carries no credentials. With include_secrets=true and a named destination (JSON only) it also includes live secrets in clear text (TSIG keys, probe credentials, the Entra client secret), so treat that file as a secret. Signing keys, refresh tokens, the audit log, node ids and the replication key are never exported.

See backup and restore.

Method Path Purpose
GET /healthz Liveness.
GET /readyz Readiness state.
GET /metrics Prometheus metrics.
GET/POST /dns-query DNS-over-HTTPS (RFC 8484), opt-in. Served by a dedicated listener on its own port (443 by default), never by the API port; the DoH listener serves nothing else.
Method Path Role Purpose
GET /api/v1/audit editor Query the audit log (filters: user_id, user_name, resource, resource_id, action, from, to, limit).
GET /api/v1/audit/export editor Export the audit log as NDJSON (same filters). Entry shape: see the operations guide.
GET /api/v1/stats read Zone / record / node / user counts.