Skip to content

REST API overview and authentication

The DTM REST API is the programmatic surface behind everything: the web UI and the Terraform provider both use it. Anything you can do in the UI, you can do over the API.

  • Base URL: https://<dtm-node>:8443
  • Auth: JWT bearer token on Authorization: Bearer <token>
  • Content type: application/json for request and response bodies

Start by logging in:

Terminal window
curl -sk -X POST https://dtm.internal:8443/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "<your-password>"}'
{
"user_id": "user-123",
"username": "admin",
"role": "admin",
"refresh_token": "3f2c..."
}

The login response sets the JWT as an HttpOnly cookie (this is what browsers and the web UI use) and returns a refresh token in the body. Scripts and API clients exchange the refresh token for a bearer token:

Terminal window
curl -sk -X POST https://dtm.internal:8443/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "3f2c..."}'
{
"token": "eyJ...",
"refresh_token": "9a1b...",
"user_id": "user-123",
"username": "admin",
"role": "admin"
}

Send the token on every subsequent request:

Terminal window
export DTM_TOKEN="eyJ..."
curl -sk https://dtm.internal:8443/api/v1/zones \
-H "Authorization: Bearer $DTM_TOKEN"

Machines should not log in like people. A service account issues a long-lived token bound to one role, made for Terraform, CI, and scripts:

Terminal window
curl -sk -X POST https://dtm.internal:8443/api/v1/service-accounts \
-H "Authorization: Bearer $DTM_TOKEN" \
-H "X-DTM-CSRF: 1" \
-d '{"name": "terraform-ci", "role": "editor", "expires_in_days": 365}'
  • The token (dtmsat_...) is returned once at creation and stored only as a hash; put it straight into your secret store. Use it exactly like a login token: Authorization: Bearer dtmsat_... (plus X-DTM-CSRF: 1 on writes and on the sensitive GETs: /api/v1/backup, /api/v1/audit/export, the zone exports, and /api/v1/debug/tail).
  • role is admin, editor, or readonly; expires_in_days is optional (0 or omitted = no expiry, maximum 3650).
  • Zone scoping (optional zone_ids array): confine the token’s writes to the listed zones, fail-closed. Scope narrows the role, never widens it: a scoped admin token is still confined, and outside its zones every state-changing request answers 403 (including zone create, the cross-zone bulk import, and every non-zone surface such as forwarders, health checks, and TSIG keys). Reads are not zone-filtered. Blank entries are rejected; scope is preserved across rotation. This is what makes a per-team pipeline token genuinely least-privileged.
  • Revoke with POST /api/v1/service-accounts/{id}/revoke; revocation and expiry are checked on every request, with no caching. List, get, delete, and a version-gated rotate (POST /api/v1/service-accounts/{id}/rotate) round out the admin-only management surface.
  • Managing DTM with Terraform? The dtm_service_account resource creates the account and mints its token through an ephemeral resource, so the secret goes to your secret store (for example Key Vault) and never lands in Terraform state. See provider setup.

Every user has one of three roles. The API enforces them per endpoint.

Role Can do
readonly List and read only.
editor All DNS data-plane writes: zones, records, health checks, forwarders, stub zones, blocklist, subnet mappings, TSIG keys, and zone import. Plus reading the audit log.
admin Everything: user management, cluster operations, backup and restore, and all runtime configuration.

A request that exceeds the token’s role returns 403 Forbidden. See users and access for creating users and assigning roles, and Entra ID SSO for single sign-on.

Terminal window
curl -sk https://dtm.internal:8443/api/v1/auth/entra/status
# {"configured": true}

The API is served over HTTPS. In production, DTM presents a certificate you trust; point your clients at the real hostname. The -k flag in these examples skips verification for convenience against a dev cluster with a self-signed certificate. Do not skip verification in production.

A few endpoints are intentionally unauthenticated because they are consumed by infrastructure that cannot carry a bearer token:

  • GET /healthz - liveness (always 200 if the process is up).
  • GET /readyz - readiness (200 only when the node is ready to serve).
  • GET /metrics - Prometheus metrics. See observability.

Gate these at the network layer (NSG / firewall) to control who can reach them.