API Documentation

Bring DNSai DNS intelligence into your AI assistant, your code and your systems: an MCP server for Claude, ChatGPT, Copilot, Gemini and 19 more AI tools, a REST API with OpenAPI 3.1, and signed webhooks for DNS changes.

Overview AI & Integrations MCP Server Authentication Getting Started AI-Assisted Dev Endpoints Reference DNS Changes Webhooks Rate Limits & Quotas Error Handling Security Best Practices Field Reference OpenAPI spec
AI tools (23) MCP server Salesforce HubSpot SIEM

Overview

DNSai puts your DNS intelligence wherever you work: in your own code through the REST API, in your AI assistant through the MCP server, in your CRM and SIEM, and in your systems the moment something changes, through signed webhooks. Domains, MX and email gateways, SPF, DKIM and DMARC, nameservers, TXT records, WHOIS, website technologies, subdomains, Organization Deep Scans and every DNS change are all available.

30MCP tools for AI assistants
23AI tools with setup guides
30+REST endpoints, described in OpenAPI 3.1
6DNS fields tracked for changes, pushed by webhook

What’s new

  • AI tools: step-by-step setup for Claude, ChatGPT, Microsoft & GitHub Copilot, Gemini, Perplexity, Grok, Cursor, Claude Code, Codex, Meta Muse, Ollama and more. See AI & Integrations.
  • Every Enterprise feature over the API and MCP: subdomains, Organization Deep Scan reports, website scans, live WHOIS, parsed email security, technology breakdowns, folders and tags, and lookups of up to 100 domains in one call.
  • DNS changes, filtered: by field, folder, tag, favorite, email-gateway switch, vendor or record text, with newest-first cursors and a stats endpoint. See DNS Changes.
  • Webhooks: new changes are pushed to your HTTPS endpoint, signed with HMAC-SHA256. See Webhooks.
  • Safer keys: up to 10 keys, each with an optional expiry and IP allowlist, plus a read-only switch for API keys and AI assistants.
  • OpenAPI 3.1: /api/v1/openapi.json for Copilot Studio, Postman, function calling and client generators.

The API and MCP server are included on Enterprise plans and above:

  • Enterprise ($499/mo) — 100 requests/min, 10,000 calls/day
  • Enterprise Max (Contact for pricing) — 500 requests/min, 50,000 calls/day
  • Enterprise Max Premium (Contact for pricing) — 2,000 requests/min, 200,000 calls/day

Limits belong to the subscription: everyone in your organization shares them, and every call counts from the first one.

All API endpoints are under https://app.dnsai.com/api/v1/ and return JSON responses in a consistent envelope format.

Create a free account   See plans →   or log in →

AI & Integrations

Prefer a traditional API? Jump to REST API and webhooks for curl, Python and JavaScript, Postman, signed webhooks and Excel, Power BI or Google Sheets, or straight to the endpoint reference.

Connect DNSai to the AI tools your team already uses, then ask in plain English: “Which of my target accounts moved to Microsoft 365 this month?”, “Summarize DMARC changes across my portfolio this week” or “Run an attack-surface review of example.com and its subdomains.” Each guide takes a few minutes.

Sign in with OAuth nothing to paste MCP with API key key kept in an environment variable OpenAPI actions / Local bridge function calling and local models REST API traditional HTTPS and JSON, no AI needed

AI assistants

Coding tools

Microsoft

Local and open models

Automation and more

REST API and webhooks

Platforms and data pipelines

Browse every guide: all AI integrations →

MCP Server

The DNSai MCP server is a remote Model Context Protocol server. Any MCP-capable assistant, including Claude, ChatGPT, Copilot, Cursor, Gemini, Perplexity and Grok, can read your domains, DNS and email-security posture, WHOIS, subdomains, website technologies, Deep Scan reports and change history, and act on them within your plan limits.

The MCP server is included on Enterprise plans. See pricing or create an account to get started.

Server URL

MCP endpoint (Streamable HTTP)
https://mcp2.dnsai.com/mcp

Connect in under a minute

Claude, ChatGPT, Perplexity, Mistral Le Chat (OAuth, no key): add a custom connector with the URL above, sign in to DNSai and approve access. In Claude: Settings → Connectors → Add custom connector.

Claude Code, Codex, Cursor, VS Code / GitHub Copilot, Gemini and other desktop or command-line tools (API key): keep the key in an environment variable and send it as a header. For example, in Claude Code:

Claude Code
claude mcp add --transport http dnsai https://mcp2.dnsai.com/mcp \
  --header "Authorization: Bearer $DNSAI_API_KEY"

Then ask: “Use DNSai whoami and tell me my plan.” Exact steps for each tool are in the AI integration guides.

Authentication

MethodUsed byHow
OAuth 2.1 + PKCE (recommended)Claude, ChatGPT, Perplexity, Mistral Le ChatThe client registers itself and you sign in and approve. Revoke it any time. Tokens carry the mcp scope.
API key headerClaude Code, Codex, Cursor, Copilot, Gemini, Grok, Windsurf, Hermes, LM Studio, n8n and moreAuthorization: Bearer dnsai_live_… from an environment variable (see Authentication). Use one expiring key per tool.
Local bridgeOllama, DeepSeek, Qwen, Llama and other local modelsA small Python script turns DNSai’s read-only endpoints into function-calling tools for any OpenAI-compatible model server.

Tools

AreaTools
Accountwhoami — plan, limits and usage shared by your organization
Domainslist_domains, get_domain, get_domain_scan_results, lookup_domains (up to 100 at once), list_folders_and_tags
Email securityget_email_security — parsed DMARC tags, DKIM selectors, SPF analysis and gateway
WHOIS, website, subdomainsget_domain_whois, get_domain_website, get_domain_subdomains, get_technology_breakdown
DNS changeslist_changes (all filters below), get_change_stats, get_domain_changes
Organization Deep Scanlist_org_scans, get_org_scan, get_org_scan_report, list_org_scan_subdomains, estimate_org_scan, start_org_scan
Fresh data on demandrescan_domain, scan_website, lookup_subdomains, refresh_whois
Organize and exportupdate_domain (star, tags, folder), add_domains, create_export (domains or changes, with a 15-minute download link), list_exports, get_job_status
Portfolioget_dashboard_summary

Guided prompts: audit_domain, triage_recent_changes, weekly_change_briefing, vendor_migration_watch, compare_spf_dmarc. Resources: dnsai://domains, dnsai://dashboard/summary, dnsai://changes/recent, dnsai://domain/{name}.

Built to be safe with AI

  • Every request is scoped to your own account’s data. Deletes, billing and admin actions are never exposed.
  • Each tool is labelled read-only or not, so your assistant can ask before it changes anything.
  • A Deep Scan spends credits, so it takes two steps: estimate_org_scan returns the cost and a 10-minute confirmation token, and start_org_scan only runs with that token after you approve.
  • Turn on read-only mode for your account or your whole organization under Settings → API Keys, and assistants can only look things up.
  • DNS, WHOIS and website text is written by each domain’s owner, so assistants are told to treat it as data, never as instructions.
  • MCP calls share your organization’s limits and are logged with the name of the connecting app.

Authentication

The API uses Bearer token authentication with API keys. Each API key is a long random string prefixed with dnsai_live_.

Creating an API Key

API access is available on Enterprise plans. Create an account or log in, then:

  1. Go to Settings > API Keys
  2. Click Create New API Key
  3. Give your key a descriptive name (e.g., "Production Server")
  4. Copy the key immediately — it is shown only once and cannot be retrieved later

You can have up to 10 active API keys at a time — use one per tool or integration so you can revoke them separately. Every key draws from the same usage pool: your plan’s limits are shared by all members of your organization, and extra keys or users don’t add allowance.

Using Your API Key

Include your API key in the Authorization header of every request:

HTTP Header
Authorization: Bearer dnsai_live_YOUR_API_KEY_HERE
Important: Never share your API key or commit it to source control. If a key is compromised, revoke it immediately from Settings and create a new one.

Getting Started

Get up and running in minutes. The examples below cover the most common operations. All responses use a consistent JSON envelope with data, meta, and errors fields.

1. Set Up Your API Key

Environment Variable (recommended)
# Store your key in an environment variable — never hardcode it
export DNSAI_API_KEY="dnsai_live_YOUR_KEY_HERE"

2. Make Your First Request

curl
curl -H "Authorization: Bearer $DNSAI_API_KEY" \
     https://app.dnsai.com/api/v1/domains/
Python
import os, requests

API_KEY = os.environ["DNSAI_API_KEY"]
headers = {"Authorization": f"Bearer {API_KEY}"}

# List your domains (default: 250 per request, max: 5000)
response = requests.get(
    "https://app.dnsai.com/api/v1/domains/",
    headers=headers,
    params={"limit": 1000},  # fetch up to 1000
)
result = response.json()
print(f"Got {len(result['data'])} of {result['meta']['total_count']} domains")

for domain in result["data"]:
    print(domain["domain"], domain["email_gateway"])
JavaScript
const API_KEY = "dnsai_live_YOUR_KEY";

const response = await fetch("https://app.dnsai.com/api/v1/domains/?limit=1000", {
  headers: { "Authorization": `Bearer ${API_KEY}` },
});
const { data, meta } = await response.json();
console.log(`Got ${data.length} of ${meta.total_count} domains`);
data.forEach(d => console.log(d.domain, d.email_gateway));

3. Fetch All Pages

For large datasets, follow meta.next to iterate through all pages. Quotas count API calls, not rows — fetching 5,000 rows counts as 1 call.

Global-view plans: on plans with the global domain view (e.g. Enterprise Max Premium), GET /api/v1/domains/ returns the entire active domain corpus, not just the domains you added, so a fetch-all loop can page through millions of rows and exhaust your daily call quota. Narrow the result with the ?search=, ?email_gateway= and ?dmarc_status= filters, or use an export, rather than paging the whole corpus.
Python — Fetch All Domains
url = "https://app.dnsai.com/api/v1/domains/?limit=5000"
all_domains = []

while url:
    response = requests.get(url, headers=headers)
    data = response.json()
    all_domains.extend(data["data"])
    url = data["meta"].get("next")  # None when no more pages

print(f"Fetched {len(all_domains)} of {data['meta']['total_count']} domains")

4. Common Operations

curl — Domain Detail (scan results + WHOIS + change history)
curl -H "Authorization: Bearer $DNSAI_API_KEY" \
     https://app.dnsai.com/api/v1/domains/123/
curl — Add Domains (up to 500 per request)
curl -X POST \
     -H "Authorization: Bearer $DNSAI_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"domains": ["example.com", "example.org"]}' \
     https://app.dnsai.com/api/v1/domains/
curl — Dashboard Summary (portfolio analytics)
curl -H "Authorization: Bearer $DNSAI_API_KEY" \
     https://app.dnsai.com/api/v1/dashboard/summary/
curl — Filter by Email Gateway
curl -H "Authorization: Bearer $DNSAI_API_KEY" \
     "https://app.dnsai.com/api/v1/domains/?email_gateway=Google+Workspace&limit=5000"

AI-Assisted Development

Use AI tools like Claude, ChatGPT, or GitHub Copilot to build your DNSai integration faster. Download the reference files below and give them to your assistant for context-aware code generation. To let an assistant use your data directly instead of writing code, connect it to the MCP server.

OpenAPI 3.1 Spec (JSON)

Machine-readable description of every API-key endpoint, always current. Import it into Postman, Copilot Studio, client generators or any function-calling setup.

Download OpenAPI Spec

API Reference (Markdown)

Comprehensive markdown document with complete field tables, code examples, and integration patterns. Paste it into any AI chat to get expert help.

Download Markdown Reference

Sample Client (Python)

Complete, runnable Python client — authentication, pagination, rate limiting, the filtered change feed, subdomains, email security, Deep Scan reports and bulk lookups.

Download Python Client
How to use: Download the JSON or Markdown file and upload it to your AI assistant along with a prompt describing what you want to build. For example:
  • "Using this API reference, build me a Python script that exports all domains with missing DMARC policies to a CSV file"
  • "Using this reference, create a Node.js integration that syncs DNSai domain data into our PostgreSQL database every hour"
  • "Help me build a dashboard widget that shows email gateway distribution from the DNSai API"

Endpoints Reference

All endpoints are relative to https://app.dnsai.com/api/v1/

API Root

MethodEndpointDescription
GET /api/v1/ API root with links to all available resources

Domains

MethodEndpointDescription
GET /api/v1/domains/ List domains with scan summary. Supports ?search=, ?email_gateway=, ?dmarc_status=, ?folder_id=, ?web_technology=, ?site_type= filters. Paginated with ?limit= and ?offset= (default 250, max 5000).
POST /api/v1/domains/ Add domains for monitoring. Body: {"domains": ["example.com", ...]}. Max 500 per request. Validates RFC 1035, rejects IPs and URLs.
GET /api/v1/domains/{id}/ Full domain detail with latest scan result, change history, and WHOIS data.
GET /api/v1/domains/{id}/scan-results/ Latest scan result for a domain (DNS records, email gateway, SPF, DKIM, DMARC, geo).
GET /api/v1/domains/{id}/changes/ DNS change history for a domain, newest first. Paginated with ?limit= and ?offset=; filter with ?field=mx,spf, ?since=, ?until=.
GET /api/v1/domains/{id}/whois/ WHOIS registration data for a domain.
GET /api/v1/domains/{id}/website/ Website technologies for a domain (latest homepage scan). 404 until the domain has been website-scanned (from the app or POST …/website/scan/).
POST /api/v1/domains/lookup/ Compact profiles for up to 100 domain names in one call. Body: {"domains": ["example.com", ...]}.
GET /api/v1/domains/{id}/email-security/ Parsed DMARC tags and analysis, DKIM selectors, SPF analysis and email gateway.
GET /api/v1/domains/{id}/subdomains/ Subdomains from Certificate Transparency, each resolved in DNS. Filters: ?resolving=1, ?search=; paginated.
POST /api/v1/domains/{id}/annotations/ Star or unstar, add or remove tags, or move to a folder. Body: {"favorite": true, "add_tags": [...], "remove_tags": [...], "folder_id": 12} (folder_id 0 = none).
GET /api/v1/changes/ Every DNS change across your workspace, oldest first. Poll with ?since_id= (the last change id you processed) and store next_since_id; first poll ?since= ISO 8601 (default: last 24 hours). ?limit= up to 1,000 (default 250); has_more means call again. Accepts every filter in DNS Changes; ?order=desc&cursor= browses newest first.
GET /api/v1/changes/stats/ Change counts for the last ?days= 1–90 (default 7): by field, by day, and which email gateways gained or lost domains.

Fresh Data on Demand

These start work right away and count against your plan’s on-demand limits. They are refused while read-only mode is on.

MethodEndpointDescription
POST /api/v1/domains/{id}/rescan/ Re-scan DNS now (counts against daily on-demand scans).
POST /api/v1/domains/{id}/website/scan/ Queue a website technology scan (hourly plan limit).
POST /api/v1/domains/{id}/subdomains/lookup/ Queue a fresh Certificate Transparency lookup (one per domain per hour).
POST /api/v1/domains/{id}/whois/refresh/ Live WHOIS lookup (hourly plan limit).

Organization Deep Scan

A Deep Scan maps a domain and all its subdomains: DNS, email security, website technologies and findings. Starting one spends credits, so it takes two calls.

MethodEndpointDescription
POST /api/v1/domains/{id}/org-scan/estimate/ Step 1: subdomain count, credit cost and a confirm_token valid for 10 minutes. Nothing is charged.
POST /api/v1/domains/{id}/org-scan/start/ Step 2: start the scan. Body: {"confirm_token": "osc_…"}. Each token works once.
GET /api/v1/org-scans/ Your Deep Scans, newest first. Filter with ?domain=.
GET /api/v1/org-scans/{job_id}/ Status and progress of a Deep Scan.
GET /api/v1/org-scans/{job_id}/report/ The report: aggregate counts, intelligence and findings.
GET /api/v1/org-scans/{job_id}/subdomains/ Per-subdomain rows. Filters: ?status=, ?search=; paginated.

Portfolio

MethodEndpointDescription
GET /api/v1/folders/ Your folders with domain counts.
GET /api/v1/tags/ Your tags with domain counts.
GET /api/v1/technologies/ Most common technologies across your domains. ?kind=, ?limit=.

Dashboard

MethodEndpointDescription
GET /api/v1/dashboard/summary/ Aggregated stats: email gateway distribution, DMARC/SPF status, scan freshness, domains needing attention.

Exports

MethodEndpointDescription
GET /api/v1/exports/ List export jobs. Paginated with ?limit= and ?offset=.
GET /api/v1/exports/{job_id}/download/ Download a completed export as CSV.
POST /api/v1/exports/ Trigger a CSV export as a background job. columns may include the DNS columns and the web_* website columns (see Website Fields). Body: {"folder_id": null, "columns": null, "filters": {"gateway": "...", "dmarc_policy": "...", "status": "...", "search": "..."}} (all optional). For DNS changes instead of domains, send {"mode": "changes", "since_date": "2026-10-01"} (up to one year back). Returns {"task_id", "status": "queued"}; poll GET /api/v1/exports/.

Account Management (signed-in session only)

These endpoints use your signed-in session, not an API key, so a leaked key can’t create more keys or change settings. Most people use Settings → API Keys instead.

MethodEndpointDescription
GET /api/v1/keys/ List your API keys (key prefix only, never the full key). Key management uses your signed-in session token, not an API key.
POST /api/v1/keys/ Create a new API key. Body: {"name": "Key Name", "expires_in_days": 90, "allowed_ips": ["203.0.113.0/24"]} (expiry 1–365 days and IP allowlist are optional). Returns the full key once.
DELETE /api/v1/keys/{id}/ Revoke an API key. The key immediately stops working.
GET POST /api/v1/webhooks/ List or add change webhooks; …/{id}/test/ sends a test delivery and …/{id}/rotate-secret/ issues a new signing secret.
GET PATCH /api/v1/access-policy/ Read or set read-only mode for your API keys and AI assistants (organization admins can set it for everyone).

Response Envelope

All responses are wrapped in a consistent JSON envelope:

JSON Response
{
  "status": "ok",
  "data": [ ... ],
  "meta": {
    "total_count": 1234,
    "limit": 250,
    "offset": 0,
    "next": "https://app.dnsai.com/api/v1/domains/?limit=250&offset=250"
  },
  "errors": []
}

meta.next and meta.previous are present only when another page exists: previous is absent on the first page and next on the last, rather than being sent as null.

For errors:

Error Response
{
  "status": "error",
  "data": null,
  "meta": {},
  "errors": [
    {
      "code": "authentication_required",
      "detail": "Authentication credentials were not provided."
    }
  ]
}

Pagination

List endpoints use limit/offset pagination designed for programmatic access. The default limit is 250, maximum 5,000 results per request.

ParameterDefaultMaxDescription
limit2505,000Number of results to return
offset0—Starting position in the result set

The response meta object includes total_count so you know how many results exist. Follow meta.next to iterate, or calculate offsets directly.

Python — Fetch All Domains
# Pull up to 5000 domains per request — no UI pagination limits
url = "https://app.dnsai.com/api/v1/domains/?limit=5000"
all_domains = []

while url:
    response = requests.get(url, headers=headers)
    data = response.json()
    all_domains.extend(data["data"])
    url = data["meta"].get("next")

print(f"Fetched {len(all_domains)} of {data['meta']['total_count']} domains")
Python — Direct Offset Access
# Jump directly to any position in the result set
response = requests.get(
    "https://app.dnsai.com/api/v1/domains/",
    headers=headers,
    params={"limit": 1000, "offset": 2000}  # rows 2001–3000
)
data = response.json()
print(f"Got {len(data['data'])} domains (offset 2000 of {data['meta']['total_count']})")

DNS Changes

DNSai tracks six fields on every domain: mx, email_gateway, dmarc_policy_p, spf, ns and txt. These filters work on /api/v1/changes/, /api/v1/changes/stats/ and the MCP list_changes tool:

ParameterMeaning
fieldOne or more tracked fields, comma-separated
domain / searchExact domain names (comma-separated) / part of a domain name
folder_id, tag, favorite=1Only domains in that folder, with that tag, or starred
gatewayDomains whose current email gateway is this, e.g. Microsoft 365
gateway_from / gateway_toEmail-gateway switches away from / to a provider
vendorChanges that added or removed a record belonging to a vendor, e.g. Salesforce (newest-first mode)
valueText inside the old or new record
since / untilISO 8601 date or datetime bounds

value searches (and, on whole-database plans, vendor and gateway searches) cover up to 31 days at a time; without since they use the last 7 days. Add a domain, folder_id or tag filter to search further back.

curl -H "Authorization: Bearer $DNSAI_API_KEY" \
  "https://app.dnsai.com/api/v1/changes/?order=desc&gateway_to=Mimecast&since=2026-10-01"

Webhooks

Add up to three HTTPS endpoints under Settings → API Keys → DNS Change Webhooks. About every 15 minutes DNSai POSTs new changes on your domains (optionally only some fields), oldest first, in batches of up to 200:

{"event": "dns.changes", "webhook_id": "…", "count": 2, "has_more": false,
 "next_since_id": 123456, "changes": [ { "id": 123455, "domain": "example.com",
   "field_name": "email_gateway", "summary": "Email gateway: Proofpoint → Microsoft 365", … } ]}

Each request carries X-DNSai-Signature: t=<unix time>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> with your signing secret. Reject anything older than five minutes or with a signature that doesn’t match:

import hashlib, hmac, time

def verify(secret: str, raw_body: bytes, header: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Answer with any 2xx within 10 seconds. Redirects are not followed. Ten failed deliveries in a row pause the webhook and leave a notice in your notification bell; resume it from Settings once the endpoint is fixed. Endpoints must be public HTTPS on port 443 or 8443.

Rate Limits & Quotas

Limits belong to your subscription. Every member of your organization and every key draws from the same per-minute limit and daily quota, and every call counts from the first one. Extra keys or users don’t add allowance. (Organizations set to per-seat limits split the plan’s allowance evenly across included seats.) The MCP server has its own allowance, also shared by the organization; ask your assistant to run whoami to see it.

PlanRate LimitDaily Quota
Enterprise100 requests/min10,000 calls/day
Enterprise Max500 requests/min50,000 calls/day
Enterprise Max Premium2,000 requests/min200,000 calls/day

Rate Limit Headers

Every API response includes these headers:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute for your plan
X-RateLimit-RemainingRequests remaining in the current minute window
X-RateLimit-ResetUnix timestamp when the rate limit window resets
X-API-Quota-LimitMaximum API calls per day for your plan
X-API-Quota-UsedAPI calls used today by everyone sharing the pool
X-API-Quota-Scopeorganization when the limits are shared by your organization, user otherwise

Handling 429 Responses

When you exceed a rate limit or quota, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.

Python — Retry Logic
import time

def api_request(url, headers, max_retries=3):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 60))
            print(f"Rate limited. Waiting {retry_after}s...")
            time.sleep(retry_after)
            continue
        return response
    raise Exception("Max retries exceeded")

Error Handling

The API uses standard HTTP status codes and returns errors in the envelope format.

StatusMeaningCommon Causes
400Bad RequestInvalid request body, validation errors
401UnauthorizedMissing or invalid API key
403ForbiddenPlan does not include API access
404Not FoundDomain not in your account, invalid endpoint
429Too Many RequestsRate limit or daily quota exceeded
500Server ErrorInternal server error (contact support)

Error Response Format

Error Response
{
  "status": "error",
  "data": null,
  "meta": {},
  "errors": [
    {
      "code": "rate_limit_exceeded",
      "detail": "Rate limit exceeded (100 requests per minute). Please retry after 45 seconds."
    }
  ]
}

Troubleshooting

  • 401 errors: Verify your API key is correct and has not been revoked. Check that the header format is exactly Authorization: Bearer dnsai_live_...
  • 403 errors: Your plan may not include API access. Upgrade to an Enterprise plan.
  • 404 errors: The domain ID must belong to your account. Use the list endpoint to find valid IDs.
  • 429 errors: Implement exponential backoff and respect the Retry-After header.

Security Best Practices

Key Storage

  • Store API keys in environment variables, never in source code
  • Use a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) for production deployments
  • Never commit API keys to version control — add them to .gitignore or .env files
Environment Variable
# .env file (never commit this)
DNSAI_API_KEY=dnsai_live_YOUR_KEY_HERE

# Python usage
import os
API_KEY = os.environ["DNSAI_API_KEY"]

Key Rotation

  • Rotate API keys periodically (every 90 days recommended)
  • Create a new key before revoking the old one to avoid downtime
  • Update all systems using the old key before revoking it
  • You can have up to 10 active keys, which allows one key per tool plus zero-downtime rotation

Network Security

  • Always use HTTPS — the API does not accept HTTP connections
  • Restrict server keys to your own IP addresses or ranges with the key’s IP allowlist (keys used by cloud AI services should not be IP-restricted)
  • Give keys an expiry, and use one key per tool or integration so you can revoke just that one
  • Turn on read-only mode under Settings → API Keys if your keys and assistants only need to look things up
  • Monitor the "Last Used" timestamp in Settings to detect unauthorized usage

Monitoring

  • Check the API Keys tab in Settings for usage stats
  • Monitor rate limit headers to stay within your plan limits
  • Set up alerts if your daily quota usage exceeds 80%
  • Revoke keys immediately if you suspect unauthorized access
Need help? Contact support at support@dnsai.com for API integration assistance or to discuss custom rate limits for your use case.

Complete Field Reference

Every field available from the DNSai API, organized by endpoint. Use this reference when building integrations to know exactly what data is available.

Domain List Fields

Returned by GET /api/v1/domains/ for each domain in the data array.

FieldTypeDescription
idintegerDomain ID in your account (use this for detail/changes/whois lookups)
domainstringThe domain name (e.g., example.com)
email_gatewaystring | nullDetected email gateway vendor (e.g., Google Workspace, Microsoft 365, Mimecast)
dmarc_policy_pstring | nullDMARC policy: reject, quarantine, none, or Not Found (no DMARC record); null if never scanned
spfstring | nullSPF record content
scanned_atdatetime | nullWhen the domain was last scanned (ISO 8601)
added_atdatetimeWhen you added this domain to your account
folder_idinteger | nullFolder ID if organized into a folder
is_favoritebooleanWhether the domain is favorited
tagsarray of stringsUser-assigned tags

Domain Detail Fields

Returned by GET /api/v1/domains/{id}/ — includes all list fields plus:

FieldTypeDescription
tldstringTop-level domain (e.g., com, org, co.uk)
statusstringDomain status: active, inactive, or removed
first_seendatetimeWhen the domain first appeared in DNSai
last_scanneddatetime | nullLast scan timestamp
scan_countintegerTotal number of scans performed
notesstring | nullYour notes about this domain
custom_labelstring | nullYour custom label for this domain
scan_resultobject | nullFull scan result (see Scan Result Fields below)
change_historyarrayRecent DNS changes (see Change History Fields below)
whoisobject | nullWHOIS data (see WHOIS Fields below)
websiteobject | nullWebsite technologies (see Website Fields below); null until scanned

Scan Result Fields

Returned by GET /api/v1/domains/{id}/scan-results/ and nested as scan_result in the domain detail response. This is the complete DNS intelligence dataset for a domain.

Core DNS Records

FieldTypeDescription
scanned_atdatetimeWhen this scan was performed
mxstring | nullMX (mail exchange) records
spfstring | nullSPF record content (v=spf1 ...)
dmarcstring | nullFull DMARC record (v=DMARC1; p=...)
dmarc_policy_pstring | nullDMARC policy value: reject, quarantine, none, or Not Found (no DMARC record)
dmarc_ruastring | nullDMARC aggregate report email address
dmarc_rufstring | nullDMARC forensic report email address
txtstring | nullTXT records (verification tokens, etc.)
a_recordsstring | nullA records (IPv4 addresses)
aaaa_recordsstring | nullAAAA records (IPv6 addresses)
nsstring | nullNameserver records
soastring | nullSOA (start of authority) record
bimistring | nullBIMI (Brand Indicators for Message Identification) record
srvstring | nullSRV (service) records
ptrstring | nullPTR (reverse DNS) records

Email Detection

FieldTypeDescription
email_gatewaystring | nullDetected email gateway vendor (e.g., Google Workspace, Microsoft 365, Mimecast, Proofpoint)
tech_in_dnsstring | nullOther technologies detected via DNS records (e.g., Salesforce, HubSpot, Zendesk)

Country, region, city, coordinates and ASN fields (mx_server_*, a_server_*, WHOIS ip_geolocation) are resolved from the IP address. This product includes IP2Location LITE data available from https://lite.ip2location.com.

MX Server Geolocation

FieldTypeDescription
mx_server_ipv4string | nullIPv4 address of primary MX server
mx_server_ipv6string | nullIPv6 address of primary MX server
mx_server_countrystring | nullCountry where MX server is hosted
mx_server_regionstring | nullRegion/state of MX server
mx_server_citystring | nullCity of MX server
mx_server_latdecimal | nullLatitude of MX server
mx_server_londecimal | nullLongitude of MX server
mx_server_asnstring | nullASN number of MX server hosting provider
mx_server_asn_namestring | nullASN name / hosting provider of MX server (e.g., Google LLC, Microsoft Corporation)

A Record Server Geolocation

FieldTypeDescription
a_server_countrystring | nullCountry where web server is hosted
a_server_regionstring | nullRegion/state of web server
a_server_citystring | nullCity of web server
a_server_latdecimal | nullLatitude of web server
a_server_londecimal | nullLongitude of web server
a_server_asnstring | nullASN number of web hosting provider
a_server_asn_namestring | nullASN name / web hosting provider (e.g., Cloudflare, Amazon, DigitalOcean)

DKIM

FieldTypeDescription
dkim_hostsstring | nullDKIM host records found in DNS
dkim_selectorsstring | nullDKIM selector names discovered (e.g., google, selector1)
dkim_vendorsstring | nullVendors identified from DKIM selectors
dkimstring | nullDKIM record content (public key data)

SPF Analysis

FieldTypeDescription
spf_main_lookupsinteger | nullNumber of DNS lookups in the top-level SPF record
spf_nested_lookupsinteger | nullNumber of DNS lookups in nested SPF includes
spf_total_lookupsinteger | nullTotal DNS lookups (RFC 7208 limit is 10)
spf_email_sendersstring | nullEmail sending services found in SPF record (e.g., Mailchimp, SendGrid)
spf_limitstring | nullSPF lookup limit status: under, at (exactly 10) or over the 10-lookup limit; empty when there is no SPF record

Change Tracking (Built into Scan Result)

FieldTypeDescription
previous_mxstring | nullPrevious MX records before last change
mx_changed_datedatetime | nullWhen MX records last changed
previous_email_gatewaystring | nullPrevious email gateway before last change
gateway_changed_datedatetime | nullWhen email gateway last changed
previous_dmarc_pstring | nullPrevious DMARC policy before last change
dmarc_p_changed_datedatetime | nullWhen DMARC policy last changed

Change History Fields

Returned by GET /api/v1/domains/{id}/changes/ and nested as change_history in the domain detail response.

FieldTypeDescription
idintegerChange record ID
domain_namestringThe domain name
field_namestringWhich DNS field changed (e.g., mx, email_gateway, dmarc_policy_p, spf)
old_valuestring | nullPrevious value
new_valuestring | nullNew value after the change
detected_atdatetimeWhen DNSai detected the change (ISO 8601)
field_labelstringReadable name of the field (e.g. “Email gateway”)
summarystringOne-line description, e.g. “Email gateway: Proofpoint → Microsoft 365”
vendor_labelsobjectFor MX, NS, SPF and TXT: added / removed entries with the vendor each one belongs to
email_gatewaystringThe domain’s current email gateway
prev_email_gatewaystringThe gateway before this change (gateway and MX changes)
domain_idintegerID to use with the /api/v1/domains/{id}/ endpoints

Record values come from public DNS and are written by the domain’s owner. Treat them as data: never execute them or pass them to an AI model as instructions.

WHOIS Fields

Returned by GET /api/v1/domains/{id}/whois/ and nested as whois in the domain detail response.

FieldTypeDescription
domain_namestringThe domain name
registrarstring | nullDomain registrar (e.g., GoDaddy, Namecheap, Cloudflare)
registration_datedatetime | nullWhen the domain was first registered
expiry_datedatetime | nullWhen the domain registration expires
updated_datedatetime | nullWhen the WHOIS record was last updated
name_serversstring | nullAuthoritative nameservers for the domain
registrant_namestring | nullRegistrant contact name (may be redacted for privacy)
registrant_orgstring | nullRegistrant organization
registrant_countrystring | nullRegistrant country
registrant_statestring | nullRegistrant state/province
contact_emailstring | nullRegistrant contact email
registrar_abuse_emailstring | nullRegistrar abuse contact email
registrar_abuse_phonestring | nullRegistrar abuse contact phone
privacy_enabledbooleanWhether WHOIS privacy/proxy service is enabled
dnssec_statusstring | nullDNSSEC signing status
domain_statusesarrayEPP domain status codes (e.g., clientTransferProhibited)
whois_statusstring | nullWHOIS lookup result status
whois_serverstring | nullWHOIS server used for the lookup
ip_geolocationobjectIP geolocation data associated with the domain
fetched_atdatetimeWhen the WHOIS data was last retrieved

Website Fields

Returned by GET /api/v1/domains/{id}/website/ and nested as website in the domain detail response. Produced by a single fetch of the site's public homepage (no JavaScript executed) plus IP/ASN correlation. Website results are shared per domain, exactly like DNS results. Scans are triggered from the Website Technologies tab in the app (10 per user per hour); the list endpoint accepts ?web_technology= and ?site_type= filters and every list row carries site_type and web_platform.

FieldTypeDescription
statusstringok, ok_limited, challenged, blocked, rate_limited, unreachable, redirect_external, parked, not_html or error
outcomeobject{status, headline, explanation, tone} — the human-readable reading of the status (protected sites are described as such, never as failures)
site_type, headlinestringe.g. E-commerce store / E-commerce store on Shopify
technologiesarray[{name, cats, version, via, confidence}] — via lists the evidence channels (header, html, script, cookie, meta, dom, js, inline loader, gtm, implied, block page)
embedsarrayThird-party hosts loaded by the page: [{host, kind, count, vendor, category}]
formsarray[{kind, method, action, action_host, inputs}]; kind is search, newsletter, contact, login, checkout or other
headersobjectSecurity-header summary: server, x_powered_by, hsts, csp, x_frame_options, x_content_type_options, referrer_policy, permissions_policy
hosting, hosting_summaryarray, objectPer A/AAAA record: ip, asn, asn_name, country, city, known_host, kind (platform/hosting/cdn), role; summary has platform_host, provider, cdn, origin_hidden
waf_vendor, http_status, fetched_url, title, generator, servervariousProtection product observed, final HTTP status and URL, page title, generator meta, server header
social_links, jsonld_types, sitemapsarraysSocial profiles found on the page, JSON-LD types, sitemap URLs from robots.txt
scanned_at, source, scanner_versiondatetime, string, stringWhen and how the row was produced (manual, deep = organization deep scan, bulk)

Dashboard Summary Fields

Returned by GET /api/v1/dashboard/summary/ — aggregated statistics across your domain portfolio.

FieldTypeDescription
total_domainsintegerTotal domains in your account
email_gateway_distributionarrayList of {gateway_name, count, percentage} objects
dmarc_distributionobjectObject with reject, quarantine, none, missing keys, each containing {count, percentage}
spf_statusobject{valid, over_limit, missing} — counts of SPF record status
scan_freshnessobject{scanned_last_24h, scanned_last_7d, percentage_24h, percentage_7d}
recent_changesarrayLast 20 DNS changes across your portfolio (last 7 days)
needs_attention_countintegerDomains with DMARC or SPF issues

Export Job Fields

Returned by GET /api/v1/exports/ and POST /api/v1/exports/.

FieldTypeDescription
idintegerExport job ID
export_typestringExport format: csv, pdf, zip, or diff
statusstringJob status: queued, processing, completed, or failed
task_idstringUnique task identifier for tracking
file_urlstring | nullDownload path, /api/v1/exports/{id}/download/ (available when status is completed). Send the same Authorization header as every other call.
row_countintegerNumber of rows in the export
file_sizeintegerFile size in bytes
parametersobjectExport parameters (columns, filters, folder_id)
created_atdatetimeWhen the export was requested
completed_atdatetime | nullWhen the export finished

API Key Fields

Returned by GET /api/v1/keys/ and POST /api/v1/keys/.

FieldTypeDescription
idintegerAPI key ID
key_prefixstringFirst 12 characters of the key (for identification)
namestringYour name for this key (e.g., “Production Server”)
created_atdatetimeWhen the key was created
last_used_atdatetime | nullWhen the key was last used for an API call
is_activebooleanWhether the key is active
expires_atdatetime | nullOptional expiration date
allowed_ipsarray | nullIP addresses or CIDR ranges the key may be used from (empty = anywhere)