QuickZTNA API
The QuickZTNA API is organized around REST. It uses action-based dispatch inside a single-handler pattern, returns JSON-encoded responses wrapped in a consistent envelope, and takes JWT bearer tokens or org-scoped API keys. 57 public endpoints across 10 logical groups.
Creates a new auth key — the credential
used by the ztna agent installer to enrol a device. Reusable
keys cover fleet rollouts (100 devices in 2 minutes via MDM or Ansible);
ephemeral keys are ideal for CI/CD runners that disappear when idle.
Authentication
Every authenticated request carries a bearer token in the
Authorization header. QuickZTNA supports three credential types:
- JWT access token — ES256-signed, 1-hour TTL. Issued by
/api/auth/loginand rotated via/api/auth/refresh. Used by the dashboard SPA. - API key — long-lived, revocable, org-scoped. Used by SCIM, Terraform, and your own automation. Created via
action: "create_api_key". - Node key — machine-to-server credential issued at first registration. Used only by the
ztnaagent for heartbeat and key exchange.
rotate_api_key on a
90-day rotation schedule.
Response envelope
Every response is wrapped in a consistent envelope. Application-level
success is in success, payload in data, and
failures populate error with a machine-readable code and a
human-readable message.
{
"success": true,
"data": { /* payload */ },
"error": null
} Create an auth key
Creates an auth key that the ztna agent uses to enrol devices
into your tailnet. The key appears in the
ZTNA_AUTH_KEY environment variable read by the installer.
Body parameters
Must be "create_auth_key". Do not use the generic "create" — that form does not exist and will 404.
The organization ID this key belongs to. You must be an admin or owner of this org.
If true, the key can enrol multiple machines. Required for fleet rollouts. Default false (one-shot key).
If true, enrolled machines auto-deregister when they go offline. Useful for CI runners and containers. Default false.
Seconds until the key becomes invalid. Minimum 300, maximum 2592000 (30 days). Default 3600 (1 hour).
Tags applied to machines enrolled with this key. Prefix with tag: — e.g. ["tag:laptop", "tag:prod"]. Used in ACL rules.
Optional human-readable note for audit. Max 255 chars.
Returns
Returns an auth_key object on success. The key
field is only returned on creation — you cannot retrieve it again later.
Authentication endpoints
POST /api/auth/signup
Create a new user account. Returns JWT pair + refresh cookie on success. Rate-limited to 5 per IP per 5 min.
$ curl https://login.quickztna.com/api/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "you@company.com",
"password": "Str0ngP@ssw0rd!",
"full_name": "Your Name"
}' {
"success": true,
"data": {
"user_id": "user_5k4aLw3Jm",
"email": "you@company.com",
"email_verified": false,
"token": "eyJhbGciOiJFUzI1NiIs...",
"refresh_token": "rt_7K9xqP4BmT...",
"expires_in": 3600
},
"error": null
}
// Set-Cookie: __Host-refresh_token=rt_...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=2592000 POST /api/auth/login
Exchange email + password for JWT pair. TOTP required if MFA is enabled (returns 403 MFA_REQUIRED if code missing). Rate-limited 10 per IP and 10 per email per 5 min.
$ curl https://login.quickztna.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "you@company.com",
"password": "Str0ngP@ssw0rd!",
"totp_code": "487392"
}' {
"success": true,
"data": {
"user_id": "user_5k4aLw3Jm",
"email": "you@company.com",
"token": "eyJhbGciOiJFUzI1NiIs...",
"refresh_token": "rt_7K9xqP4BmT...",
"expires_in": 3600
},
"error": null
} POST /api/auth/refresh
Rotate the access token. Browsers send __Host-refresh_token cookie automatically; CLI sends refresh_token in body. Server deletes old refresh token and issues a new pair (single-use rotation).
$ curl https://login.quickztna.com/api/auth/refresh \
-X POST \
-b "__Host-refresh_token=rt_..." \
-d '{}'
# CLI / non-browser: POST /api/auth/refresh with {"refresh_token":"rt_..."} {
"success": true,
"data": {
"token": "eyJhbGciOiJFUzI1NiIs...",
"refresh_token": "rt_8Ly4rN2xQ...",
"expires_in": 3600
},
"error": null
} GET /api/auth/me
Return the authenticated user's profile. Checks JWT, checks blacklist (returns 401 if password was changed).
$ curl https://login.quickztna.com/api/auth/me \
-H "Authorization: Bearer $TOKEN" {
"success": true,
"data": {
"id": "user_5k4aLw3Jm",
"email": "you@company.com",
"full_name": "Your Name",
"avatar_url": null
},
"error": null
} POST /api/auth/mfa/setup
Generate a new TOTP secret. Scan the returned URI with an authenticator, then call /api/auth/mfa/verify with the first 6-digit code to activate + receive 10 backup codes.
$ curl https://login.quickztna.com/api/auth/mfa/setup \
-X POST \
-H "Authorization: Bearer $TOKEN" {
"success": true,
"data": {
"secret": "JBSWY3DPEHPK3PXP",
"uri": "otpauth://totp/QuickZTNA:you@company.com?secret=JBSWY3DPEHPK3PXP&issuer=QuickZTNA"
},
"error": null
} API keys
POST /api/key-management (action: "create_api_key")
Create a long-lived, revocable API key for machine-to-machine use (SCIM, Terraform, CI/CD). Admin-only.
$ curl https://login.quickztna.com/api/key-management \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "create_api_key",
"org_id": "org_9fX2kRmBnqT4",
"name": "terraform-ci",
"scopes": ["machines:write", "acl:write", "dns:write"]
}' {
"success": true,
"data": {
"id": "ak_Pq7r2wXK",
"key": "qz-api-live-4N8mR3w...",
"name": "terraform-ci",
"scopes": ["machines:write", "acl:write", "dns:write"],
"created_at": "2026-04-19T18:00:00Z",
"revoked": false
},
"error": null
} Machines
POST /api/register-machine
Agent-side registration. Called by the ztna installer with an auth key. Server generates node_key, allocates tailnet IP, returns machine ID.
name (not hostname). The backend silently ignores hostname.
$ curl https://login.quickztna.com/api/register-machine \
-H "Authorization: Bearer tskey-auth-abc123" \
-H "Content-Type: application/json" \
-d '{
"name": "laptop-alex",
"os": "linux",
"wireguard_pubkey": "abcdef0123...",
"pqc_pubkey": "ML-KEM-768 bytes..."
}' {
"success": true,
"data": {
"machine_id": "m_Pq7r2wXK",
"node_key": "nk_live_...",
"tailnet_ip": "100.64.1.42",
"magicdns_name": "laptop-alex.acme.zt.net",
"derp_region": "blr",
"status": "online"
},
"error": null
} POST /api/machine-heartbeat
Called by the agent every 60 seconds. Returns peer list, DNS config, ACL rules, and any pending admin commands (wipe, lock, config push).
$ curl https://login.quickztna.com/api/machine-heartbeat \
-H "Authorization: Bearer $NODE_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "online",
"endpoint": "52.34.12.88:41824",
"derp_region": "blr",
"posture": { "os_version": "24.04", "disk_encryption": true, "firewall": "ufw" }
}' {
"success": true,
"data": {
"peers": [ /* 99 peer objects */ ],
"dns_config": {
"nameservers": ["100.100.100.100"],
"search_domain": "acme.zt.net"
},
"acl_rules": [ /* evaluated rules */ ],
"pending_commands": [],
"posture_verdict": "compliant",
"quarantined": false
},
"error": null
} POST /api/machine-admin (action: "approve")
Approve a machine pending admin review. Required when org has approval_required enabled.
$ curl https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "approve",
"org_id": "org_9fX2kR",
"machine_id": "m_Pq7r2w"
}' {
"success": true,
"data": {
"machine_id": "m_Pq7r2w",
"status": "online",
"approved_at": "2026-04-19T18:00:00Z",
"approved_by": "user_5k4aLw"
},
"error": null
} POST /api/machine-admin (action: "quarantine")
Isolate a machine. Its tailnet IP stays allocated but ACL evaluation denies all traffic until unquarantined. Used for compromised devices or posture failures.
$ curl https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "quarantine",
"org_id": "org_9fX2kR",
"machine_id": "m_Pq7r2w",
"reason": "Posture failure: OS version outdated"
}' {
"success": true,
"data": { "machine_id": "m_Pq7r2w", "status": "quarantined" },
"error": null
} POST /api/machine-admin (action: "wipe_device")
Issue a remote wipe command. Delivered via next heartbeat. The agent wipes local config, state, tokens, and uninstalls the service. Audit-logged with actor identity.
$ curl https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "wipe_device",
"org_id": "org_9fX2kR",
"machine_id": "m_Pq7r2w",
"confirm": "WIPE-m_Pq7r2w"
}' Policies (ACL)
POST /api/acl-evaluate
Evaluate a hypothetical connection against current ACLs. Returns allow/deny + the matched rule (or why no rule matched).
$ curl https://login.quickztna.com/api/acl-evaluate \
-H "Authorization: Bearer $TOKEN" \
-d '{
"org_id": "org_9fX2kR",
"source_machine_id": "m_laptop_alex",
"destination_machine_id": "m_db_primary",
"port": 5432,
"protocol": "tcp",
"time_hour": 14,
"day_of_week": "thu",
"source_country": "IN"
}' {
"success": true,
"data": {
"verdict": "ALLOW",
"matched_rule_id": "acl_7K9xq",
"rule_name": "laptops-to-prod-business-hours",
"evaluated_conditions": {
"src_tag": "tag:laptop ✓",
"dst_tag": "tag:prod ✓",
"port": "5432 in 22,443,5432 ✓",
"time": "14:00 in 09-18 ✓",
"source_country": "IN ✓"
}
},
"error": null
} DNS
POST /api/dns-management
Manage MagicDNS configuration — nameservers, search domain, resolver behaviour.
get_settings (not list_records). The older name is documented elsewhere but will 404.
$ curl https://login.quickztna.com/api/dns-management \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "get_settings",
"org_id": "org_9fX2kR"
}' {
"success": true,
"data": {
"magic_dns": true,
"search_domain": "acme.zt.net",
"nameservers": ["1.1.1.1", "1.0.0.1"],
"override_local_dns": false
},
"error": null
} POST /api/dns-filter (action: "update_policy")
Update DNS blocklist + allowlist policy. Feature-gated by dns_filtering (free on all plans).
$ curl https://login.quickztna.com/api/dns-filter \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "update_policy",
"org_id": "org_9fX2kR",
"blocklist_categories": ["malware", "phishing", "ads"],
"allow_domains": ["github.com", "*.github.com"],
"block_domains": ["tiktok.com"]
}' {
"success": true,
"data": {
"policy_id": "dnsp_K9xq",
"blocklist_categories": ["malware", "phishing", "ads"],
"allow_domains": ["github.com", "*.github.com"],
"block_domains": ["tiktok.com"],
"updated_at": "2026-04-19T18:00:00Z"
},
"error": null
} AI assistant
POST /api/ai-assist (action: "chat")
Conversational Q&A about your tenant. Claude-powered. Gated by ai_chat (free on all plans).
$ curl https://login.quickztna.com/api/ai-assist \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "chat",
"org_id": "org_9fX2kR",
"message": "Which machines went offline in the last hour?",
"conversation_id": null
}' {
"success": true,
"data": {
"conversation_id": "conv_K9xq",
"answer": "Two machines went offline in the last hour: legacy-vpc (offline 47min ago, reason: heartbeat timeout) and eu-edge-07 (offline 12min ago, reason: scheduled maintenance). No others.",
"context_used": ["machines", "heartbeats"]
},
"error": null
} POST /api/ai-assist (action: "generate_acl")
English → ACL rule. Returns a ready-to-apply JSON policy. Gated by nl_acl_builder (free on all plans).
$ curl https://login.quickztna.com/api/ai-assist \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "generate_acl",
"org_id": "org_9fX2kR",
"description": "Engineers can SSH to prod servers 9-6 IST on weekdays from India"
}' {
"success": true,
"data": {
"rule": {
"name": "eng-ssh-prod-business-hours",
"src": "tag:engineer",
"dst": "tag:prod",
"proto": "tcp",
"ports": "22",
"time_hour": "09-18",
"day_of_week": "mon-fri",
"source_country": "IN"
},
"explanation": "Generated ACL matches tag:engineer source to tag:prod dest on SSH (port 22), restricted to weekdays 09:00–18:00 IST from India."
},
"error": null
} Governance
POST /api/governance (action: "jit_request")
Request time-bounded access to a resource. An approver gets notified; on approval the grant is active for the window and auto-revokes.
$ curl https://login.quickztna.com/api/governance \
-H "Authorization: Bearer $TOKEN" \
-d '{
"action": "jit_request",
"org_id": "org_9fX2kR",
"resource_type": "machine",
"resource_id": "m_db_primary",
"requested_duration": 3600,
"justification": "Debugging elevated-privilege migration issue #4782"
}' {
"success": true,
"data": {
"grant_id": "jit_Pq7r2w",
"status": "pending",
"approvers_notified": ["alice@acme.com", "bob@acme.com"],
"expires_at": "2026-04-19T19:00:00Z",
"requested_at": "2026-04-19T18:00:00Z"
},
"error": null
} Errors
QuickZTNA uses conventional HTTP status codes. 2xx = success, 4xx = client
error, 5xx = server-side. Every non-2xx response populates
error.code with a machine-readable string.
Pagination
List endpoints use limit + offset pagination. Pass limit and
range_from/range_to as query parameters.
Page size, 1–100. Default 20.
Offset to start from. Default 0.
Offset to end at (exclusive).
Column to sort by. Must be an indexed column.
Sort direction. Default true.
Rate limits
Rate limits are applied per IP and per email on authentication endpoints,
and per org on everything else. The client IP is detected via
cf-connecting-ip first (not spoofable), falling back to
x-real-ip.
Exceeding a limit returns 429 RATE_LIMITED. Login lockout
after 10 failed password attempts per email = 15-minute block; unlock is
automatic at the expiry timestamp.
WebSocket realtime
QuickZTNA exposes a single WebSocket at /api/realtime for
live updates on machines, members, audit, keys, posture, threats, certs,
DNS, ACL, billing, and remote desktop channels.
Sec-WebSocket-Protocol header as
access_token.<jwt> — never in the URL. This prevents
tokens from leaking to proxy logs, CDN caches, or browser history.
const ws = new WebSocket(
'wss://login.quickztna.com/api/realtime?org_id=org_...',
[`access_token.${jwt}`]
);
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'subscribe',
channels: ['machines', 'audit']
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
// { type: 'event', channel: 'machines', event: 'UPDATE', payload: {...} }
}; Deprecated fields
Some older field/action names silently fail or 404. Use the forms on the left — not the forms on the right.
$ curl https://login.quickztna.com/api/key-management \
-H "Authorization: Bearer $QZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "create_auth_key",
"org_id": "org_9fX2kRmBnqT4",
"reusable": true,
"ephemeral": false,
"expires_in": 3600,
"tags": ["tag:laptop", "tag:prod"]
}' {
"success": true,
"data": {
"id": "ak_9fX2kRmBnqT4",
"key": "tskey-auth-abc123def456...",
"org_id": "org_9fX2kRmBnqT4",
"reusable": true,
"ephemeral": false,
"expires_at": "2026-04-19T18:00:00Z",
"tags": ["tag:laptop", "tag:prod"],
"created_by": "user_5k4aLw3Jm",
"created_at": "2026-04-19T17:00:00Z",
"revoked": false
},
"error": null
}