API reference
Manage tenants, networks, rules and logs from your own tools. Every request is JSON over HTTPS.
Base URL#
https://api.dnsafe.net/api/v1
Authentication#
Create an API key in the portal under API Keys and send it as a bearer token. Keys start with dnsapi_, for example dnsapi_xxxxxxxxxxxxxxxx. The full key is shown once. Keep it in a secret manager, never in a web page or app that users can download.
curl https://api.dnsafe.net/api/v1/tenants \
-H "Authorization: Bearer $DNSAFE_API_KEY"A key acts as the account that created it: a partner’s key can only reach that partner’s tenants. For safety, keys can’t manage API keys, billing, logins or passwords. Those need a portal sign-in. Revoke a key any time from the API Keys page; it stops working immediately.
Rate limits#
| Tier | Requests / minute | Price |
|---|---|---|
| Basic | 100 | $29/mo |
| Pro | 1,000 | $79/mo |
| Enterprise | Unlimited | $199/mo |
Included: Basic with Business, MSP Growth and trials; Pro with Business Enterprise and MSP Enterprise. Or buy a tier on the API keys page.
Over the limit you’ll get 429 Too Many Requests with a Retry-After header (seconds until the next minute starts).
Errors#
Errors return JSON with a detail message.
| Status | Meaning |
|---|---|
400 | Invalid request (e.g. nothing to update) |
401 | Missing, invalid or revoked key, or the account is inactive |
402 | Plan limit reached or feature not in your plan, or the key’s plan has lapsed |
403 | Not your tenant, or an endpoint API keys can’t use |
404 | Not found |
409 | IP already registered |
422 | Validation failed. detail lists the fields. |
429 | Rate limit reached. Wait for Retry-After. |
503 | Service unavailable. Try again shortly. |
Tenants#
List tenants
Every tenant your key can access, newest first.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/tenants \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants`, { headers: AUTH }); console.log(await r.json());
{
"tenants": [
{
"id": "3007624d-68b4-475f-91dd-9f5a798e68c9",
"name": "Harbor Logistics",
"active": true,
"created_at": "2026-04-26T03:44:40Z",
"partner_name": "Acme IT",
"ip_count": 2,
"rule_count": 4
}
]
}Create a tenant
Adds a new client under your partner account. New networks count toward your plan’s included networks.
Parameters
nameREQUIREDstring | Display name for the client. |
curl -X POST https://api.dnsafe.net/api/v1/tenants \ -H "Authorization: Bearer $DNSAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Northwind Legal"}'
import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.post(f"{BASE}/tenants", headers=AUTH, json={"name": "Northwind Legal"}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants`, { method: "POST", headers: { ...AUTH, "Content-Type": "application/json" }, body: JSON.stringify({"name": "Northwind Legal"}) }); console.log(await r.json());
{
"id": "3007624d-68b4-475f-91dd-9f5a798e68c9",
"name": "Northwind Legal",
"partner_id": "5f9b915d-\u2026",
"active": true,
"created_at": "2026-09-26T14:02:11Z"
}Get a tenant
One tenant’s details.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/tenants/$TENANT \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}`, { headers: AUTH }); console.log(await r.json());
{
"id": "3007624d-68b4-475f-91dd-9f5a798e68c9",
"name": "Harbor Logistics",
"active": true,
"notes": null,
"created_at": "2026-04-26T03:44:40Z",
"safesearch_enforcement": false,
"block_ads": true,
"https_block_page": false,
"block_proxyware": true,
"partner_id": "5f9b915d-\u2026",
"partner_name": "Acme IT"
}Update a tenant
Change the name, notes or filtering switches. Send only the fields you want to change. Changes to the switches apply to the resolver right away.
Parameters
namestring | New display name. |
notesstring | Internal notes. |
safesearch_enforcementboolean | Force SafeSearch on Google, Bing and YouTube. |
block_adsboolean | Block ads and trackers. Default true. |
https_block_pageboolean | Show the block page on HTTPS sites. Default false. |
block_proxywareboolean | Block bandwidth-sharing apps and proxy SDKs. Default true. |
curl -X PATCH https://api.dnsafe.net/api/v1/tenants/$TENANT \ -H "Authorization: Bearer $DNSAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"safesearch_enforcement": true}'
import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.patch(f"{BASE}/tenants/{tenant}", headers=AUTH, json={"safesearch_enforcement": True}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}`, { method: "PATCH", headers: { ...AUTH, "Content-Type": "application/json" }, body: JSON.stringify({"safesearch_enforcement": true}) }); console.log(await r.json());
{
"message": "updated"
}Pause or resume
Pauses filtering for a network, or turns it back on. Each call flips the current state and returns the new one. Turning a network back on counts toward your plan’s networks.
Parameters
| No parameters. |
curl -X PATCH https://api.dnsafe.net/api/v1/tenants/$TENANT/active \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.patch(f"{BASE}/tenants/{tenant}/active", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/active`, { method: "PATCH", headers: AUTH }); console.log(await r.json());
{
"id": "3007624d-68b4-475f-91dd-9f5a798e68c9",
"active": false
}Get a summary
Lookup totals for a network across its stored logs: all lookups, blocked lookups, threats (ThreatGrid blocks) and the time of the last lookup. total_logs is the same as total_queries.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/tenants/$TENANT/summary \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/summary", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/summary`, { headers: AUTH }); console.log(await r.json());
{
"total_queries": 48211,
"total_logs": 48211,
"total_blocked": 1937,
"total_threats": 42,
"last_seen": "2026-10-15T18:22:04+00:00"
}Networks (IPs)#
The resolver identifies a tenant by the public IP its lookups come from. Register each office’s public IPv4 address (or a range up to /16).
List IPs
Registered networks for a tenant. auto_update is null unless the IP has an auto-update link (IP Updater); then it shows when the link was last used and the result.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/tenants/$TENANT/ips \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/ips", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/ips`, { headers: AUTH }); console.log(await r.json());
{
"ips": [
{
"id": "a41e\u2026",
"client_ip": "203.0.113.10",
"note": "Main office",
"created_at": "2026-09-20T10:00:00Z",
"auto_update": null
}
]
}Register an IP
Starts filtering lookups from this network within a few minutes.
Parameters
client_ipREQUIREDstring | Public IPv4 address or CIDR, e.g. 203.0.113.10 or 203.0.113.0/28. |
notestring | Label shown in the portal. |
curl -X POST https://api.dnsafe.net/api/v1/tenants/$TENANT/ips \ -H "Authorization: Bearer $DNSAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"client_ip": "203.0.113.10", "note": "Main office"}'
import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.post(f"{BASE}/tenants/{tenant}/ips", headers=AUTH, json={"client_ip": "203.0.113.10", "note": "Main office"}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/ips`, { method: "POST", headers: { ...AUTH, "Content-Type": "application/json" }, body: JSON.stringify({"client_ip": "203.0.113.10", "note": "Main office"}) }); console.log(await r.json());
{
"id": "a41e\u2026",
"client_ip": "203.0.113.10",
"note": "Main office",
"created_at": "2026-09-26T14:02:11Z"
}Remove an IP
Stops filtering for that network.
Parameters
| No parameters. |
curl -X DELETE https://api.dnsafe.net/api/v1/tenants/$TENANT/ips/$IP_ID \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.delete(f"{BASE}/tenants/{tenant}/ips/{ip_id}", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/ips/${ipId}`, { method: "DELETE", headers: AUTH }); console.log(await r.json());
{
"message": "deleted"
}Rules#
List rules
All allow/block rules on a tenant, A→Z by domain.
Parameters
actionstring | Filter: block or allow. |
searchstring | Domain contains… |
curl "https://api.dnsafe.net/api/v1/tenants/$TENANT/rules?action=block" \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/rules", headers=AUTH, params={"action": "block"}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/rules?action=block`, { headers: AUTH }); console.log(await r.json());
{
"rules": [
{
"id": "b1c4\u2026",
"domain": "tiktok.com",
"action": "block",
"note": null,
"days": 31,
"time_start": "08:00:00",
"time_end": "15:00:00",
"created_at": "2026-09-26T14:02:11Z"
}
]
}Add a rule
Blocks or allows a domain (and its subdomains) for one tenant. Add days and times to make it a scheduled rule. The days and time fields need a plan with policy schedules; otherwise you get 402.
Parameters
domainREQUIREDstring | e.g. tiktok.com. Subdomains are included. |
actionREQUIRED"block" | "allow" | What to do with matching lookups. |
daysinteger 1–127 | Days bitmask: Mon=1, Tue=2, Wed=4, Thu=8, Fri=16, Sat=32, Sun=64. Default 127 (every day). |
time_start / time_end"HH:MM" | Active window (server time, US Eastern). Overnight windows like 22:00–06:00 work. Omit for all day. |
notestring | Shown in the portal. |
curl -X POST https://api.dnsafe.net/api/v1/tenants/$TENANT/rules \ -H "Authorization: Bearer $DNSAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "tiktok.com", "action": "block", "days": 31, "time_start": "08:00", "time_end": "15:00"}'
import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.post(f"{BASE}/tenants/{tenant}/rules", headers=AUTH, json={"domain": "tiktok.com", "action": "block", "days": 31, "time_start": "08:00", "time_end": "15:00"}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/rules`, { method: "POST", headers: { ...AUTH, "Content-Type": "application/json" }, body: JSON.stringify({"domain": "tiktok.com", "action": "block", "days": 31, "time_start": "08:00", "time_end": "15:00"}) }); console.log(await r.json());
{
"id": "b1c4\u2026",
"domain": "tiktok.com",
"action": "block",
"note": null,
"days": 31,
"time_start": "08:00:00",
"time_end": "15:00:00",
"created_at": "2026-09-26T14:02:11Z"
}Bulk import
Add many rules at once. Each item takes the same fields as “Add a rule”. Duplicates are skipped.
Parameters
rulesREQUIREDarray | List of rule objects. |
curl -X POST https://api.dnsafe.net/api/v1/tenants/$TENANT/rules/bulk \ -H "Authorization: Bearer $DNSAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"rules": [{"domain": "example-casino.com", "action": "block"}, {"domain": "partner-portal.com", "action": "allow"}]}'
import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.post(f"{BASE}/tenants/{tenant}/rules/bulk", headers=AUTH, json={"rules": [{"domain": "example-casino.com", "action": "block"}, {"domain": "partner-portal.com", "action": "allow"}]}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/rules/bulk`, { method: "POST", headers: { ...AUTH, "Content-Type": "application/json" }, body: JSON.stringify({"rules": [{"domain": "example-casino.com", "action": "block"}, {"domain": "partner-portal.com", "action": "allow"}]}) }); console.log(await r.json());
{
"imported": 2
}curl -X DELETE https://api.dnsafe.net/api/v1/tenants/$TENANT/rules/$RULE \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.delete(f"{BASE}/tenants/{tenant}/rules/{rule}", headers=AUTH) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/rules/${rule}`, { method: "DELETE", headers: AUTH }); console.log(await r.json());
{
"message": "deleted"
}Delete rules in bulk
Removes several rules in one call. IDs that don’t belong to this tenant are ignored. An ID that isn’t a valid UUID returns 400.
Parameters
rule_idsREQUIREDarray of strings | IDs of the rules to delete. |
curl -X DELETE https://api.dnsafe.net/api/v1/tenants/$TENANT/rules/bulk \ -H "Authorization: Bearer $DNSAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"rule_ids": ["b1c4…", "c7d2…"]}'
import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.delete(f"{BASE}/tenants/{tenant}/rules/bulk", headers=AUTH, json={"rule_ids": ["b1c4…", "c7d2…"]}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/rules/bulk`, { method: "DELETE", headers: { ...AUTH, "Content-Type": "application/json" }, body: JSON.stringify({"rule_ids": ["b1c4…", "c7d2…"]}) }); console.log(await r.json());
{
"message": "deleted"
}Test a domain
What would happen to a domain on this network right now, and why. It checks SafeSearch, the network’s rules (including schedules), then the threat, ad, bandwidth-sharing and category lists. Device group rules aren’t checked; groups_note is true when the network has groups. verdict is allowed, blocked or safesearch. Limited to 30 tests a minute.
Parameters
domainREQUIREDstring | The domain to test, e.g. tiktok.com. |
curl "https://api.dnsafe.net/api/v1/tenants/$TENANT/policy-test?domain=tiktok.com" \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/policy-test", headers=AUTH, params={"domain": "tiktok.com"}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/policy-test?domain=tiktok.com`, { headers: AUTH }); console.log(await r.json());
{
"domain": "tiktok.com",
"network": "Harbor Logistics",
"verdict": "blocked",
"reason": "Blocked by your block rule for tiktok.com.",
"source": "Your rules",
"rule": {
"domain": "tiktok.com",
"action": "block",
"note": null,
"scheduled": false
},
"threat_listed": false,
"categories": ["Social media"],
"sources": [],
"inactive_categories": ["Social media"],
"groups_note": false
}IP groups#
Groups let part of a tenant’s network (say, a guest Wi-Fi range) have its own rules. Group rules are checked before tenant rules.
| Method | Path | What it does |
|---|---|---|
| GET | /tenants/{tenant_id}/groups | List groups |
| POST | /tenants/{tenant_id}/groups | Create — {"name", "description"} |
| DELETE | /tenants/{tenant_id}/groups/{group_id} | Delete a group |
| GET | …/groups/{group_id}/members | List member ranges |
| POST | …/groups/{group_id}/members | Add a range — {"cidr", "note"} |
| DELETE | …/groups/{group_id}/members/{member_id} | Remove a range |
| GET | …/groups/{group_id}/rules | List group rules |
| POST | …/groups/{group_id}/rules | Add a group rule — same fields as tenant rules |
| DELETE | …/groups/{group_id}/rules/{rule_id} | Delete a group rule |
Logs#
Query logs
Recent DNS lookups for a tenant, newest first, with a total count for paging.
Parameters
limitinteger | 1–1000, default 100. |
offsetinteger | For paging. |
actionstring | blocked (any block), allowed (an allow rule matched), passed (resolved normally, including SafeSearch rewrites), or one exact value: block_threat (threat feed), block (other ThreatGrid list entries), block_tenant, block_sched, block_group, block_group_sched, block_cat_ads (ads and trackers), block_proxyware, block_pack_* (content filters, e.g. block_pack_gambling), block_doh, allow_tenant, allow_sched, allow_group, allow_group_sched, allow, safesearch_google, safesearch_bing, safesearch_youtube. |
searchstring | Domain contains… |
curl "https://api.dnsafe.net/api/v1/tenants/$TENANT/logs?action=blocked&limit=50" \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/logs", headers=AUTH, params={"action": "blocked", "limit": "50"}) print(r.json())
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/logs?action=blocked&limit=50`, { headers: AUTH }); console.log(await r.json());
{
"logs": [
{
"id": 91822,
"client_ip": "203.0.113.10",
"domain": "c2-beacon.xyz",
"action": "block_threat",
"logged_at": "2026-09-26T14:02:11Z"
}
],
"total": 284
}Export CSV
Downloads the latest 10,000 lookups as a CSV file (client IP, domain, action, time). Limited to 30 exports an hour, shared with the PDF report.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/tenants/$TENANT/export/csv \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/export/csv", headers=AUTH) print(r.text)
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const r = await fetch(`${BASE}/tenants/${tenant}/export/csv`, { headers: AUTH }); console.log(await r.text());
client_ip,domain,action,logged_at 203.0.113.10,c2-beacon.xyz,block_threat,2026-09-26 14:02:11
Export PDF report
Downloads a PDF report for the network: status, rules and a summary of the latest 200 lookups. Counts toward the 30 exports an hour.
Parameters
| No parameters. |
curl -o report.pdf https://api.dnsafe.net/api/v1/tenants/$TENANT/export/pdf \
-H "Authorization: Bearer $DNSAFE_API_KEY"import os, requests BASE = "https://api.dnsafe.net/api/v1" AUTH = {"Authorization": f"Bearer {os.environ['DNSAFE_API_KEY']}"} r = requests.get(f"{BASE}/tenants/{tenant}/export/pdf", headers=AUTH) open("report.pdf", "wb").write(r.content)
const BASE = "https://api.dnsafe.net/api/v1"; const AUTH = { Authorization: `Bearer ${process.env.DNSAFE_API_KEY}` }; const { writeFile } = await import("node:fs/promises"); const r = await fetch(`${BASE}/tenants/${tenant}/export/pdf`, { headers: AUTH }); await writeFile("report.pdf", Buffer.from(await r.arrayBuffer()));
A PDF file (Content-Type: application/pdf)
Public data#
No API key needed. These are the same numbers shown on dnsafe.net.
Network totals
Totals across every DNSAFE network for the last 24 hours, how many domains are on the threat list, and DNS uptime over the last 30 days (all three regions). Totals only: never domains, networks or customers. Refreshed every 10 minutes; 60 requests a minute per IP. A total is null if it couldn’t be read.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/public/network-stats
{
"window_hours": 24,
"threats_blocked": 48210,
"lookups": 3104522,
"threat_list_domains": 612034,
"dns_uptime_30d": 99.97,
"updated": "2026-10-16T23:40:00+00:00"
}Live status
One line of live service status, the same as status.dnsafe.net. level is up, deg (degraded, an incident or maintenance), down, or unk when status can’t be confirmed right now. title is only there when there’s more detail, like which services are affected. Refreshed every 60 seconds; 60 requests a minute per IP.
Parameters
| No parameters. |
curl https://api.dnsafe.net/api/v1/public/status-summary
{
"level": "up",
"text": "All systems operational"
}ThreatDNS API (free beta)#
Checking domains and IPs against threat sources is a separate API on threatdns.com, with its own keys. Make one in API Keys → ThreatDNS API in the portal (or Settings → ThreatDNS API at my.dnsafe.net). ThreatDNS keys start with tdk_live_ and only run checks; they can’t reach networks or settings, and DNSAFE dnsapi_ keys don’t work there.
curl "https://threatdns.com/v1/check?q=login-micros0ft.top" \ -H "Authorization: Bearer $THREATDNS_KEY"
500 checks a month (1,000 with a business or MSP plan, or Home Pro), bulk checks of up to 100, and repeating a check you made in the last day is free. Full reference: threatdns.com/api. How to read a report, email checks and limits: the ThreatDNS guide.