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. 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 |
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 |
403 | Not your tenant, or an endpoint API keys can’t use |
404 | Not found |
422 | Validation failed — detail lists the fields |
429 | Rate limit reached — wait for Retry-After |
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.
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,
"safesearch_enforcement": false,
"partner_id": "5f9b915d-\u2026",
"partner_name": "Acme IT"
}Update a tenant
Change the name, notes or SafeSearch. Send only the fields you want to change.
Parameters
namestring | New display name. |
notesstring | Internal notes. |
safesearch_enforcementboolean | Force SafeSearch on Google, Bing and YouTube. |
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"
}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.
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"
}
]
}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"
}
]
}Add a rule
Blocks or allows a domain (and its subdomains) for one tenant. Add days and times to make it a scheduled rule.
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
}Delete a rule
Removes one rule. To delete several, send DELETE /tenants/{{tenant_id}}/rules/bulk with {{"rule_ids": [...]}}.
Parameters
| No parameters. |
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"
}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 | Exact action recorded, e.g. block or allow. |
searchstring | Domain contains… |
curl "https://api.dnsafe.net/api/v1/tenants/$TENANT/logs?action=block&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": "block", "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=block&limit=50`, { headers: AUTH }); console.log(await r.json());
{
"logs": [
{
"id": 91822,
"client_ip": "203.0.113.10",
"domain": "c2-beacon.xyz",
"action": "block",
"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).
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.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}/export/csv`, { headers: AUTH }); console.log(await r.json());
client_ip,domain,action,logged_at 203.0.113.10,c2-beacon.xyz,block,2026-09-26 14:02:11