{
  "api_name": "DNSai REST API",
  "version": "v1",
  "base_url": "https://app.dnsai.com/api/v1",
  "description": "The DNSai REST API provides programmatic access to DNS intelligence data including domain monitoring, scan results, WHOIS registration data, DNS change history, email gateway detection, SPF/DKIM/DMARC analysis, and MX/A record geolocation.",
  "authentication": {
    "type": "bearer_token",
    "header": "Authorization",
    "format": "Bearer dnsai_live_YOUR_API_KEY",
    "key_prefix": "dnsai_live_",
    "key_management_url": "https://app.dnsai.com/settings/?tab=apikeys",
    "max_active_keys": 3,
    "notes": "API keys are shown only once at creation. Store securely in environment variables or a secrets manager."
  },
  "response_envelope": {
    "description": "All responses use a consistent JSON envelope.",
    "format": {
      "status": "string — 'ok' or 'error'",
      "data": "object | array | null — the response payload",
      "meta": "object — pagination metadata (total_count, limit, offset, next, previous)",
      "errors": "array — list of error objects with 'code' and 'detail' fields"
    },
    "example_success": {
      "status": "ok",
      "data": [],
      "meta": {
        "total_count": 1234,
        "limit": 250,
        "offset": 0,
        "next": "https://app.dnsai.com/api/v1/domains/?limit=250&offset=250",
        "previous": null
      },
      "errors": []
    },
    "example_error": {
      "status": "error",
      "data": null,
      "meta": {},
      "errors": [
        {
          "code": "authentication_required",
          "detail": "Authentication credentials were not provided."
        }
      ]
    }
  },
  "pagination": {
    "type": "limit_offset",
    "default_limit": 250,
    "max_limit": 5000,
    "parameters": {
      "limit": {
        "type": "integer",
        "default": 250,
        "max": 5000,
        "description": "Number of results to return per request"
      },
      "offset": {
        "type": "integer",
        "default": 0,
        "description": "Starting position in the result set"
      }
    },
    "response_fields": {
      "total_count": "Total number of results available",
      "limit": "Limit used for this request",
      "offset": "Offset used for this request",
      "next": "Full URL for the next page (null if no more results)",
      "previous": "Full URL for the previous page (null if at start)"
    },
    "notes": "Quotas count API calls, not rows returned. Fetching 5000 rows counts as 1 API call."
  },
  "rate_limits": {
    "plans": {
      "enterprise": {
        "price": "$99/mo",
        "rate_limit": "100 requests/min",
        "daily_quota": "10,000 calls/day"
      },
      "enterprise_max": {
        "price": "$2,500/mo",
        "rate_limit": "500 requests/min",
        "daily_quota": "50,000 calls/day"
      },
      "enterprise_max_premium": {
        "price": "$5,000/mo",
        "rate_limit": "2,000 requests/min",
        "daily_quota": "200,000 calls/day"
      }
    },
    "headers": {
      "X-RateLimit-Limit": "Maximum requests per minute for your plan",
      "X-RateLimit-Remaining": "Requests remaining in the current minute window",
      "X-RateLimit-Reset": "Unix timestamp when the rate limit window resets",
      "X-API-Quota-Limit": "Maximum API calls per day for your plan",
      "X-API-Quota-Used": "API calls used today"
    },
    "exceeded_response": {
      "status_code": 429,
      "header": "Retry-After",
      "description": "Seconds to wait before retrying"
    }
  },
  "error_codes": {
    "400": "Bad Request — invalid request body or validation errors",
    "401": "Unauthorized — missing or invalid API key",
    "403": "Forbidden — plan does not include API access",
    "404": "Not Found — domain not in your account or invalid endpoint",
    "429": "Too Many Requests — rate limit or daily quota exceeded",
    "500": "Server Error — internal error, contact support@dnsai.com"
  },
  "endpoints": [
    {
      "name": "API Root",
      "method": "GET",
      "path": "/api/v1/",
      "description": "Returns API version info, user details, and available endpoint paths.",
      "authentication": "required",
      "pagination": false,
      "response_fields": {
        "version": {"type": "string", "description": "API version (v1)"},
        "user": {"type": "string", "description": "Authenticated user's email"},
        "plan": {"type": "string", "description": "User's plan name"},
        "pagination": {"type": "object", "description": "Default pagination settings"},
        "endpoints": {"type": "object", "description": "Map of endpoint names to URL paths"}
      }
    },
    {
      "name": "List Domains",
      "method": "GET",
      "path": "/api/v1/domains/",
      "description": "List all domains in your account with scan summary data. Supports filtering and pagination.",
      "authentication": "required",
      "pagination": true,
      "query_parameters": {
        "search": {"type": "string", "description": "Filter by domain name (case-insensitive contains)"},
        "email_gateway": {"type": "string", "description": "Filter by email gateway vendor (exact match, case-insensitive)"},
        "dmarc_status": {"type": "string", "description": "Filter by DMARC policy: reject, quarantine, none, or missing"},
        "folder_id": {"type": "integer", "description": "Filter by folder ID"},
        "limit": {"type": "integer", "default": 250, "max": 5000, "description": "Results per page"},
        "offset": {"type": "integer", "default": 0, "description": "Starting position"}
      },
      "response_fields": {
        "id": {"type": "integer", "description": "Domain ID in your account (use for detail/changes/whois lookups)"},
        "domain": {"type": "string", "description": "Domain name (e.g., example.com)"},
        "email_gateway": {"type": "string|null", "description": "Detected email gateway (e.g., Google Workspace, Microsoft 365, Mimecast)"},
        "dmarc_policy_p": {"type": "string|null", "description": "DMARC policy: reject, quarantine, none, or null"},
        "spf": {"type": "string|null", "description": "SPF record content"},
        "scanned_at": {"type": "datetime|null", "description": "Last scan timestamp (ISO 8601)"},
        "added_at": {"type": "datetime", "description": "When domain was added to your account"},
        "folder_id": {"type": "integer|null", "description": "Folder ID if organized"},
        "is_favorite": {"type": "boolean", "description": "Whether domain is favorited"},
        "tags": {"type": "array[string]", "description": "User-assigned tags"}
      }
    },
    {
      "name": "Add Domains",
      "method": "POST",
      "path": "/api/v1/domains/",
      "description": "Add domains for monitoring. Validates RFC 1035 format, rejects IPs and URLs. Automatically queues scans for new domains.",
      "authentication": "required",
      "pagination": false,
      "request_body": {
        "domains": {
          "type": "array[string]",
          "required": true,
          "min_length": 1,
          "max_length": 500,
          "description": "List of domain names to add (max 500 per request)",
          "example": ["example.com", "example.org"]
        }
      },
      "response_fields": {
        "domains_submitted": {"type": "integer", "description": "Number of domains in the request"},
        "domains_added": {"type": "integer", "description": "Number of new domains added"},
        "domains_existing": {"type": "integer", "description": "Number already in your account"},
        "scans_queued": {"type": "integer", "description": "Number of scans queued for new domains"}
      },
      "response_status": 201
    },
    {
      "name": "Domain Detail",
      "method": "GET",
      "path": "/api/v1/domains/{id}/",
      "description": "Full domain detail including latest scan result, change history (last 100), and WHOIS data.",
      "authentication": "required",
      "pagination": false,
      "path_parameters": {
        "id": {"type": "integer", "description": "Domain ID from the list endpoint"}
      },
      "response_fields": {
        "id": {"type": "integer", "description": "Domain ID"},
        "domain": {"type": "string", "description": "Domain name"},
        "tld": {"type": "string", "description": "Top-level domain (e.g., com, org, co.uk)"},
        "status": {"type": "string", "description": "Domain status: active, inactive, or removed"},
        "first_seen": {"type": "datetime", "description": "When domain first appeared in DNSai"},
        "last_scanned": {"type": "datetime|null", "description": "Last scan timestamp"},
        "scan_count": {"type": "integer", "description": "Total scans performed"},
        "added_at": {"type": "datetime", "description": "When added to your account"},
        "folder_id": {"type": "integer|null", "description": "Folder ID"},
        "is_favorite": {"type": "boolean", "description": "Whether favorited"},
        "tags": {"type": "array[string]", "description": "User-assigned tags"},
        "notes": {"type": "string|null", "description": "Your notes about this domain"},
        "custom_label": {"type": "string|null", "description": "Your custom label"},
        "scan_result": {"type": "object|null", "description": "Full scan result (see Scan Result fields)"},
        "change_history": {"type": "array", "description": "Recent DNS changes (see Change History fields)"},
        "whois": {"type": "object|null", "description": "WHOIS data (see WHOIS fields)"}
      }
    },
    {
      "name": "Scan Results",
      "method": "GET",
      "path": "/api/v1/domains/{id}/scan-results/",
      "description": "Latest scan result for a domain. Returns the complete DNS intelligence dataset.",
      "authentication": "required",
      "pagination": false,
      "path_parameters": {
        "id": {"type": "integer", "description": "Domain ID from the list endpoint"}
      },
      "response_fields": {
        "scanned_at": {"type": "datetime", "description": "When this scan was performed"},
        "mx": {"type": "string|null", "description": "MX (mail exchange) records"},
        "spf": {"type": "string|null", "description": "SPF record content (v=spf1 ...)"},
        "dmarc": {"type": "string|null", "description": "Full DMARC record (v=DMARC1; p=...)"},
        "dmarc_policy_p": {"type": "string|null", "description": "DMARC policy: reject, quarantine, none"},
        "dmarc_rua": {"type": "string|null", "description": "DMARC aggregate report email"},
        "dmarc_ruf": {"type": "string|null", "description": "DMARC forensic report email"},
        "txt": {"type": "string|null", "description": "TXT records"},
        "a_records": {"type": "string|null", "description": "A records (IPv4 addresses)"},
        "aaaa_records": {"type": "string|null", "description": "AAAA records (IPv6 addresses)"},
        "ns": {"type": "string|null", "description": "Nameserver records"},
        "soa": {"type": "string|null", "description": "SOA (start of authority) record"},
        "bimi": {"type": "string|null", "description": "BIMI record"},
        "srv": {"type": "string|null", "description": "SRV (service) records"},
        "ptr": {"type": "string|null", "description": "PTR (reverse DNS) records"},
        "email_gateway": {"type": "string|null", "description": "Detected email gateway vendor"},
        "tech_in_dns": {"type": "string|null", "description": "Other technologies detected in DNS"},
        "mx_server_ipv4": {"type": "string|null", "description": "IPv4 of primary MX server"},
        "mx_server_ipv6": {"type": "string|null", "description": "IPv6 of primary MX server"},
        "mx_server_country": {"type": "string|null", "description": "Country of MX server"},
        "mx_server_region": {"type": "string|null", "description": "Region/state of MX server"},
        "mx_server_city": {"type": "string|null", "description": "City of MX server"},
        "mx_server_lat": {"type": "decimal|null", "description": "Latitude of MX server"},
        "mx_server_lon": {"type": "decimal|null", "description": "Longitude of MX server"},
        "mx_server_asn": {"type": "string|null", "description": "ASN number of MX hosting provider"},
        "mx_server_asn_name": {"type": "string|null", "description": "ASN name / MX hosting provider"},
        "a_server_country": {"type": "string|null", "description": "Country of web server"},
        "a_server_region": {"type": "string|null", "description": "Region/state of web server"},
        "a_server_city": {"type": "string|null", "description": "City of web server"},
        "a_server_lat": {"type": "decimal|null", "description": "Latitude of web server"},
        "a_server_lon": {"type": "decimal|null", "description": "Longitude of web server"},
        "a_server_asn": {"type": "string|null", "description": "ASN number of web hosting provider"},
        "a_server_asn_name": {"type": "string|null", "description": "ASN name / web hosting provider"},
        "dkim_hosts": {"type": "string|null", "description": "DKIM host records found"},
        "dkim_selectors": {"type": "string|null", "description": "DKIM selector names discovered"},
        "dkim_vendors": {"type": "string|null", "description": "Vendors identified from DKIM selectors"},
        "dkim": {"type": "string|null", "description": "DKIM record content (public key)"},
        "spf_main_lookups": {"type": "integer|null", "description": "DNS lookups in top-level SPF"},
        "spf_nested_lookups": {"type": "integer|null", "description": "DNS lookups in nested SPF includes"},
        "spf_total_lookups": {"type": "integer|null", "description": "Total DNS lookups (RFC 7208 limit is 10)"},
        "spf_email_senders": {"type": "string|null", "description": "Email sending services in SPF record"},
        "spf_limit": {"type": "string|null", "description": "SPF lookup limit status"},
        "previous_mx": {"type": "string|null", "description": "Previous MX records before last change"},
        "mx_changed_date": {"type": "datetime|null", "description": "When MX records last changed"},
        "previous_email_gateway": {"type": "string|null", "description": "Previous email gateway before last change"},
        "gateway_changed_date": {"type": "datetime|null", "description": "When email gateway last changed"},
        "previous_dmarc_p": {"type": "string|null", "description": "Previous DMARC policy before last change"},
        "dmarc_p_changed_date": {"type": "datetime|null", "description": "When DMARC policy last changed"}
      }
    },
    {
      "name": "Change History",
      "method": "GET",
      "path": "/api/v1/domains/{id}/changes/",
      "description": "DNS change history for a domain. Tracks changes to MX records, email gateway, DMARC policy, SPF, and other DNS fields.",
      "authentication": "required",
      "pagination": true,
      "path_parameters": {
        "id": {"type": "integer", "description": "Domain ID from the list endpoint"}
      },
      "response_fields": {
        "id": {"type": "integer", "description": "Change record ID"},
        "domain_name": {"type": "string", "description": "The domain name"},
        "field_name": {"type": "string", "description": "Which DNS field changed (e.g., mx, email_gateway, dmarc_policy_p, spf)"},
        "old_value": {"type": "string|null", "description": "Previous value"},
        "new_value": {"type": "string|null", "description": "New value after the change"},
        "detected_at": {"type": "datetime", "description": "When DNSai detected the change (ISO 8601)"}
      }
    },
    {
      "name": "WHOIS Data",
      "method": "GET",
      "path": "/api/v1/domains/{id}/whois/",
      "description": "WHOIS registration data for a domain.",
      "authentication": "required",
      "pagination": false,
      "path_parameters": {
        "id": {"type": "integer", "description": "Domain ID from the list endpoint"}
      },
      "response_fields": {
        "domain_name": {"type": "string", "description": "The domain name"},
        "registrar": {"type": "string|null", "description": "Domain registrar"},
        "registration_date": {"type": "datetime|null", "description": "First registered date"},
        "expiry_date": {"type": "datetime|null", "description": "Registration expiry date"},
        "updated_date": {"type": "datetime|null", "description": "Last WHOIS record update"},
        "name_servers": {"type": "string|null", "description": "Authoritative nameservers"},
        "registrant_name": {"type": "string|null", "description": "Registrant contact name"},
        "registrant_org": {"type": "string|null", "description": "Registrant organization"},
        "registrant_country": {"type": "string|null", "description": "Registrant country"},
        "registrant_state": {"type": "string|null", "description": "Registrant state/province"},
        "contact_email": {"type": "string|null", "description": "Registrant contact email"},
        "registrar_abuse_email": {"type": "string|null", "description": "Registrar abuse email"},
        "registrar_abuse_phone": {"type": "string|null", "description": "Registrar abuse phone"},
        "privacy_enabled": {"type": "boolean", "description": "WHOIS privacy service enabled"},
        "dnssec_status": {"type": "string|null", "description": "DNSSEC signing status"},
        "domain_statuses": {"type": "array", "description": "EPP domain status codes"},
        "whois_status": {"type": "string|null", "description": "WHOIS lookup result status"},
        "whois_server": {"type": "string|null", "description": "WHOIS server used"},
        "ip_geolocation": {"type": "object", "description": "IP geolocation data"},
        "fetched_at": {"type": "datetime", "description": "When WHOIS data was retrieved"}
      }
    },
    {
      "name": "Dashboard Summary",
      "method": "GET",
      "path": "/api/v1/dashboard/summary/",
      "description": "Aggregated statistics across your domain portfolio including email gateway distribution, DMARC compliance, SPF status, scan freshness, and recent changes.",
      "authentication": "required",
      "pagination": false,
      "response_fields": {
        "total_domains": {"type": "integer", "description": "Total domains in your account"},
        "email_gateway_distribution": {"type": "array", "description": "List of {gateway_name, count, percentage} objects"},
        "dmarc_distribution": {"type": "object", "description": "{reject, quarantine, none, missing} — each with {count, percentage}"},
        "spf_status": {"type": "object", "description": "{valid, over_limit, missing} — counts of SPF record status"},
        "scan_freshness": {"type": "object", "description": "{scanned_last_24h, scanned_last_7d, percentage_24h, percentage_7d}"},
        "recent_changes": {"type": "array", "description": "Last 20 DNS changes across portfolio (7-day window)"},
        "needs_attention_count": {"type": "integer", "description": "Domains with DMARC or SPF issues"}
      }
    },
    {
      "name": "List Exports",
      "method": "GET",
      "path": "/api/v1/exports/",
      "description": "List your export jobs, most recent first.",
      "authentication": "required",
      "pagination": true,
      "response_fields": {
        "id": {"type": "integer", "description": "Export job ID"},
        "export_type": {"type": "string", "description": "Format: csv, pdf, zip, or diff"},
        "status": {"type": "string", "description": "Job status: queued, processing, completed, or failed"},
        "task_id": {"type": "string", "description": "Unique task identifier"},
        "file_url": {"type": "string|null", "description": "Download URL when completed"},
        "row_count": {"type": "integer", "description": "Number of rows exported"},
        "file_size": {"type": "integer", "description": "File size in bytes"},
        "parameters": {"type": "object", "description": "Export parameters (columns, filters, folder_id)"},
        "created_at": {"type": "datetime", "description": "When export was requested"},
        "completed_at": {"type": "datetime|null", "description": "When export finished"}
      }
    },
    {
      "name": "Trigger Export",
      "method": "POST",
      "path": "/api/v1/exports/",
      "description": "Trigger a CSV export as a background job. Poll the exports list endpoint to check status.",
      "authentication": "required",
      "pagination": false,
      "request_body": {
        "folder_id": {"type": "integer|null", "required": false, "description": "Filter export to a specific folder"},
        "columns": {"type": "array[string]|null", "required": false, "description": "Specific columns to export (null = all columns)"},
        "filters": {
          "type": "object",
          "required": false,
          "description": "Filter the export",
          "allowed_keys": {
            "gateway": "Filter by email gateway",
            "status": "Filter by domain status",
            "dmarc_policy": "Filter by DMARC policy",
            "search": "Search by domain name"
          }
        }
      },
      "response_fields": {
        "task_id": {"type": "string", "description": "Job ID to track the export"},
        "status": {"type": "string", "description": "Initial status (queued)"}
      },
      "response_status": 202
    },
    {
      "name": "List API Keys",
      "method": "GET",
      "path": "/api/v1/keys/",
      "description": "List your API keys (key prefix only, never the full key).",
      "authentication": "required",
      "pagination": false,
      "response_fields": {
        "id": {"type": "integer", "description": "API key ID"},
        "key_prefix": {"type": "string", "description": "First 12 characters of the key"},
        "name": {"type": "string", "description": "Your name for this key"},
        "created_at": {"type": "datetime", "description": "When created"},
        "last_used_at": {"type": "datetime|null", "description": "Last API call timestamp"},
        "is_active": {"type": "boolean", "description": "Whether key is active"},
        "expires_at": {"type": "datetime|null", "description": "Optional expiration date"}
      }
    },
    {
      "name": "Create API Key",
      "method": "POST",
      "path": "/api/v1/keys/",
      "description": "Create a new API key. The full key is returned only once — store it securely.",
      "authentication": "required",
      "pagination": false,
      "request_body": {
        "name": {"type": "string", "required": true, "description": "Descriptive name for the key"}
      },
      "response_status": 201
    },
    {
      "name": "Revoke API Key",
      "method": "DELETE",
      "path": "/api/v1/keys/{id}/",
      "description": "Revoke an API key. The key immediately stops working.",
      "authentication": "required",
      "pagination": false,
      "path_parameters": {
        "id": {"type": "integer", "description": "API key ID"}
      },
      "response_status": 204
    }
  ],
  "code_examples": {
    "python_list_all_domains": {
      "language": "python",
      "description": "Fetch all domains using limit/offset pagination",
      "code": "import requests\n\nAPI_KEY = \"dnsai_live_YOUR_KEY\"\nheaders = {\"Authorization\": f\"Bearer {API_KEY}\"}\nurl = \"https://app.dnsai.com/api/v1/domains/?limit=5000\"\nall_domains = []\n\nwhile url:\n    response = requests.get(url, headers=headers)\n    data = response.json()\n    all_domains.extend(data[\"data\"])\n    url = data[\"meta\"].get(\"next\")\n\nprint(f\"Fetched {len(all_domains)} domains\")"
    },
    "python_scan_results": {
      "language": "python",
      "description": "Get scan results for a domain",
      "code": "response = requests.get(\n    f\"https://app.dnsai.com/api/v1/domains/{domain_id}/scan-results/\",\n    headers=headers,\n)\nscan = response.json()[\"data\"]\nprint(f\"Gateway: {scan['email_gateway']}\")\nprint(f\"MX: {scan['mx']}\")\nprint(f\"SPF: {scan['spf']}\")\nprint(f\"DMARC: {scan['dmarc_policy_p']}\")"
    },
    "python_add_domains": {
      "language": "python",
      "description": "Add domains for monitoring",
      "code": "response = requests.post(\n    \"https://app.dnsai.com/api/v1/domains/\",\n    headers=headers,\n    json={\"domains\": [\"example.com\", \"example.org\"]},\n)\nresult = response.json()[\"data\"]\nprint(f\"Added: {result['domains_added']}, Existing: {result['domains_existing']}\")"
    },
    "python_rate_limit_handling": {
      "language": "python",
      "description": "Handle rate limiting with retry logic",
      "code": "import time\n\ndef api_request(url, headers, max_retries=5):\n    for attempt in range(max_retries):\n        response = requests.get(url, headers=headers)\n        if response.status_code == 429:\n            retry_after = int(response.headers.get(\"Retry-After\", 60))\n            print(f\"Rate limited. Waiting {retry_after}s...\")\n            time.sleep(retry_after)\n            continue\n        response.raise_for_status()\n        return response.json()\n    raise Exception(\"Max retries exceeded\")"
    },
    "javascript_fetch": {
      "language": "javascript",
      "description": "Fetch domains using JavaScript fetch API",
      "code": "const API_KEY = \"dnsai_live_YOUR_KEY\";\n\nconst response = await fetch(\"https://app.dnsai.com/api/v1/domains/\", {\n  headers: { \"Authorization\": `Bearer ${API_KEY}` },\n});\nconst { data, meta } = await response.json();\nconsole.log(`Got ${data.length} of ${meta.total_count} domains`);"
    },
    "curl_examples": {
      "language": "bash",
      "description": "Common curl commands",
      "code": "# List domains\ncurl -H \"Authorization: Bearer dnsai_live_YOUR_KEY\" \\\n     https://app.dnsai.com/api/v1/domains/\n\n# Get domain detail\ncurl -H \"Authorization: Bearer dnsai_live_YOUR_KEY\" \\\n     https://app.dnsai.com/api/v1/domains/123/\n\n# Add domains\ncurl -X POST \\\n     -H \"Authorization: Bearer dnsai_live_YOUR_KEY\" \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\"domains\": [\"example.com\", \"example.org\"]}' \\\n     https://app.dnsai.com/api/v1/domains/\n\n# Dashboard summary\ncurl -H \"Authorization: Bearer dnsai_live_YOUR_KEY\" \\\n     https://app.dnsai.com/api/v1/dashboard/summary/"
    }
  }
}
