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. |