{
  "openapi": "3.1.0",
  "info": {
    "title": "DNSai API",
    "version": "1.0.0",
    "description": "Free DNS & email-security lookups for AI agents. One domain per request — for bulk / multi-domain use the DNSai Enterprise API (https://app.dnsai.com/pricing/). Also available as a remote MCP server at https://mcp.dnsai.com/mcp.",
    "contact": {
      "url": "https://dnsai.com/api/"
    }
  },
  "servers": [
    {
      "url": "https://api.dnsai.com/v1"
    }
  ],
  "paths": {
    "/dns/{domain}": {
      "get": {
        "operationId": "dns_lookup",
        "summary": "Look up live DNS records for ONE domain.",
        "description": "Look up live DNS records for ONE domain. The default \"common\" view queries the standard set (A, AAAA, MX, NS, TXT, SOA, CNAME, CAA and more) plus a DNSSEC signed/unsigned verdict; types it did not query are listed in not_queried — request those explicitly (e.g. type=\"DNSKEY\") to check them. Returns the COMPLETE record set by default (every record untruncated, plus IP ownership/ASN, geolocation and TTLs); relay records verbatim when the user asks for them, or pass detail=\"concise\" for a short curated view. Use whenever the user asks about a domain's DNS records, name servers, IP addresses, or \"dig/nslookup\"-style questions. Returns live data from public resolvers with a plain-English summary. Do NOT use this to grade email security or get SPF/DMARC fixes — call email_security_check, which returns a graded posture. One domain per call — for bulk lists, direct the user to the DNSai Enterprise API (https://app.dnsai.com). Example: dns_lookup(domain=\"acme.com\", type=\"MX\").",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single domain (one per request — bulk is rejected)."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "DNS record type. \"common\" = the standard set; a single type returns that record set only.",
            "schema": {
              "type": "string",
              "enum": [
                "common",
                "A",
                "AAAA",
                "MX",
                "NS",
                "TXT",
                "SOA",
                "CNAME",
                "CAA",
                "DNSKEY",
                "DS",
                "PTR",
                "SRV"
              ],
              "default": "common"
            }
          },
          {
            "name": "resolver",
            "in": "query",
            "required": false,
            "description": "Upstream resolver.",
            "schema": {
              "type": "string",
              "enum": [
                "google",
                "cloudflare"
              ],
              "default": "google"
            }
          },
          {
            "name": "detail",
            "in": "query",
            "required": false,
            "description": "Output detail. \"full\" (default) = the complete lookup, every record untruncated plus hosting/ASN/TTL; \"concise\" = short curated view.",
            "schema": {
              "type": "string",
              "enum": [
                "full",
                "concise"
              ],
              "default": "full"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/email-security/{domain}": {
      "get": {
        "operationId": "email_security_check",
        "summary": "One call answers \"is this domain's email properly secured?\" — checks MX, SPF, DMARC and mail-gateway/provider detection for ONE domain.",
        "description": "One call answers \"is this domain's email properly secured?\" — checks MX, SPF, DMARC and mail-gateway/provider detection for ONE domain. The differentiator vs a plain DNS resolver. Use when the user asks about email deliverability, spoofing protection, SPF/DMARC posture, or \"is email set up correctly\". Prefer this over dns_lookup, spf_analyze or dmarc_check for any \"is the email secure / well-configured\" question — it returns a graded verdict with prioritized fixes, followed by every underlying DNS record (complete by default; detail=\"concise\" for the grade + key records only). For DKIM signing keys, use dkim_discover. One domain per call.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single domain (one per request — bulk is rejected)."
          },
          {
            "name": "detail",
            "in": "query",
            "required": false,
            "description": "Output detail. \"full\" (default) = graded posture plus every underlying DNS record; \"concise\" = grade, checks, fixes and MX/SPF/DMARC only.",
            "schema": {
              "type": "string",
              "enum": [
                "full",
                "concise"
              ],
              "default": "full"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/whois/{query}": {
      "get": {
        "operationId": "whois_lookup",
        "summary": "WHOIS / RDAP registration data for ONE domain or IP — registrar, status, creation/expiry, name servers.",
        "description": "WHOIS / RDAP registration data for ONE domain or IP — registrar, status, creation/expiry, name servers. Returns publicly published WHOIS/RDAP data as-is. Use for \"who owns\", \"when does it expire\", \"registrar\", \"when was it registered\". One target per call.",
        "parameters": [
          {
            "name": "query",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single query (one per request — bulk is rejected)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/spf/{domain}": {
      "get": {
        "operationId": "spf_analyze",
        "summary": "Analyze a domain's SPF record for ONE domain — the include tree and the RFC 10-DNS-lookup-limit verdict.",
        "description": "Analyze a domain's SPF record for ONE domain — the include tree and the RFC 10-DNS-lookup-limit verdict. Use only when the question is specifically about SPF (include tree, lookup count, the `all` qualifier). For an overall \"is email secure\" grade, use email_security_check instead. One domain per call.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single domain (one per request — bulk is rejected)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/dmarc/{domain}": {
      "get": {
        "operationId": "dmarc_check",
        "summary": "Fetch and parse the DMARC policy for ONE domain — presence, p= policy, and the raw record.",
        "description": "Fetch and parse the DMARC policy for ONE domain — presence, p= policy, and the raw record. Use for DMARC-only questions (\"does this domain have DMARC\", \"is it p=reject/quarantine/none\"). For full email posture (SPF + DMARC + MX, graded), use email_security_check instead. One domain per call.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single domain (one per request — bulk is rejected)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/dkim/{domain}": {
      "get": {
        "operationId": "dkim_discover",
        "summary": "Discover the DKIM signing keys published for ONE domain.",
        "description": "Discover the DKIM signing keys published for ONE domain. Returns the signing keys found, the mail providers they map to, and their selectors. Use when the user asks about DKIM, signing keys, or \"does this domain sign its mail?\". Note: a domain can publish DKIM under a private selector that discovery won't surface, so \"none found\" is not proof of \"no DKIM\". One domain per call.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single domain (one per request — bulk is rejected)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/blacklist/{query}": {
      "get": {
        "operationId": "blacklist_check",
        "summary": "Check whether ONE domain or IP is listed on major email blacklists / RBLs (Spamhaus, Barracuda, SpamCop, SORBS, UCEPROTECT and more).",
        "description": "Check whether ONE domain or IP is listed on major email blacklists / RBLs (Spamhaus, Barracuda, SpamCop, SORBS, UCEPROTECT and more). For a domain, checks the domain itself, its website (A-record) IP AND its mail-server (MX) IPs — each verdict names the IP and role it applies to, since website and mail reputation can differ. Returns a listed/clean verdict with the specific lists. One target per call.",
        "parameters": [
          {
            "name": "query",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A single query (one per request — bulk is rejected)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/validate": {
      "get": {
        "operationId": "validate",
        "summary": "Validate the syntax of an SPF or DMARC record (no DNS lookup).",
        "description": "Validate the SYNTAX of an SPF or DMARC record string (no DNS lookup) — returns valid/invalid, a score, a letter grade, and a parsed breakdown. Use when the user pastes an SPF/DMARC record and asks \"is this correct?\".",
        "parameters": [
          {
            "name": "record",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The raw SPF/DMARC record string."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "spf",
                "dmarc",
                "auto"
              ]
            },
            "description": "Record type (default auto)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or bulk/multi-domain rejected (single_lookup_only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: daily free limit reached (see upgrade; Retry-After = seconds to UTC midnight). origin_busy: the engine is throttling this client — retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "origin_unreachable / origin_error / origin_bad_json — the lookup engine failed; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "origin_timeout — the lookup took too long; nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/meta/limits": {
      "get": {
        "operationId": "get_limits",
        "summary": "The caller's current daily usage and remaining quota.",
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tier": {
                      "type": "string"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "used": {
                      "type": "integer"
                    },
                    "remaining": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/request-key": {
      "post": {
        "operationId": "request_api_key",
        "summary": "Get a free API key (100/day) by verifying an email — no password, no signup.",
        "description": "Send the user's email; they receive a verification link and, once clicked, a free key that raises the daily limit from 10 to 100 lookups/day. Use when the caller hits the anonymous rate limit or asks to upgrade. After they verify, they paste the emailed key as an Authorization: Bearer dk_free_… header.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The user's email address."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification email sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Envelope": {
        "type": "object",
        "properties": {
          "tool": {
            "type": "string"
          },
          "query": {
            "type": "object",
            "additionalProperties": true
          },
          "result": {
            "type": "object",
            "additionalProperties": true,
            "description": "Raw engine data (display-formatted strings; kept for back-compat)."
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Clean machine values (DNS tools): meta (rcode, nxdomain, dnssec_ad, resolver, per-type ttl) + record arrays (A/AAAA/NS…) with MX parsed to {priority,exchange}."
          },
          "report": {
            "type": "string",
            "description": "The complete lookup as markdown: verdict first, then every field the engine returned (all tools)."
          },
          "summary": {
            "type": "string",
            "description": "One-sentence plain-English answer — quote this."
          },
          "source": {
            "type": "string",
            "description": "Deep link to the matching dnsai.com tool with the query prefilled."
          },
          "fetched_at": {
            "type": "string",
            "format": "date-time"
          },
          "cache": {
            "type": "string",
            "enum": [
              "live",
              "cached"
            ]
          },
          "usage": {
            "type": "object",
            "properties": {
              "remaining_today": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "tier": {
                "type": "string"
              },
              "limit_basis": {
                "type": "string",
                "enum": [
                  "ip",
                  "api_key"
                ],
                "description": "Anonymous quota is per IPv4 address / per IPv6 /64."
              },
              "key_note": {
                "type": "string",
                "description": "Present when a key was sent but not accepted (revoked/mistyped) — the anonymous allowance was used."
              }
            }
          },
          "upgrade": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "reason": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "tool",
          "result",
          "summary",
          "source",
          "usage"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "tool": {
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "upgrade": {
            "type": [
              "object",
              "null"
            ]
          }
        },
        "required": [
          "error",
          "message"
        ]
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Optional dk_ API key for higher limits (free to register)."
      }
    }
  }
}