{
  "openapi": "3.0.3",
  "info": {
    "title": "spf — SPF Checker API",
    "description": "Looks up a domain's SPF record (RFC 7208), parses its mechanisms, lints it for common problems (multiple records, the deprecated ptr mechanism, +all, exceeding the 10 DNS-lookup limit) and — when an ip is supplied — evaluates whether that IP is authorised to send mail, returning pass/fail/softfail/neutral/none/permerror/temperror with a trace. Evaluation performs live DNS lookups; parsing and linting are local.",
    "version": "1.0.0"
  },
  "servers": [
    { "url": "https://spf.benjamin-dreier.de", "description": "Production" }
  ],
  "paths": {
    "/v1/check": {
      "get": {
        "summary": "Look up, lint and (optionally) evaluate a domain's SPF record",
        "operationId": "check",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "The domain whose SPF record should be checked.",
            "schema": { "type": "string" },
            "example": "example.com"
          },
          {
            "name": "ip",
            "in": "query",
            "required": false,
            "description": "An IPv4 or IPv6 address to evaluate against the record. When present, the response includes an evaluation.",
            "schema": { "type": "string" },
            "example": "192.0.2.10"
          }
        ],
        "responses": {
          "200": {
            "description": "The parsed record with warnings, and an evaluation when ip was supplied. A domain without an SPF record is reported here (found=false with an error message), not as a 4xx.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CheckResponse" },
                "example": {
                  "domain": "example.com",
                  "found": true,
                  "record": "v=spf1 ip4:192.0.2.0/24 include:_spf.example.net -all",
                  "terms": [
                    { "raw": "ip4:192.0.2.0/24", "qualifier": "+", "kind": "ip4", "value": "192.0.2.0/24" },
                    { "raw": "include:_spf.example.net", "qualifier": "+", "kind": "include", "value": "_spf.example.net" },
                    { "raw": "-all", "qualifier": "-", "kind": "all" }
                  ],
                  "warnings": [],
                  "evaluation": {
                    "result": "pass",
                    "matched": "ip4:192.0.2.0/24",
                    "explanation": "the IP is authorised to send mail for this domain",
                    "dns_lookups": 0,
                    "trace": ["example.com: ip4:192.0.2.0/24 matched"]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing 'domain' or an invalid 'ip'.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "missing 'domain' parameter" }
              }
            }
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "summary": "Service health and version",
        "operationId": "health",
        "responses": {
          "200": { "description": "Service ready." }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CheckResponse": {
        "type": "object",
        "required": ["domain", "found", "warnings"],
        "properties": {
          "domain": { "type": "string", "example": "example.com" },
          "found": { "type": "boolean", "description": "Whether a single v=spf1 record was found." },
          "record": { "type": "string", "description": "The raw SPF record.", "example": "v=spf1 ip4:192.0.2.0/24 -all" },
          "terms": { "type": "array", "items": { "$ref": "#/components/schemas/Term" } },
          "warnings": { "type": "array", "items": { "type": "string" }, "description": "Lint findings; empty when the record is clean." },
          "evaluation": { "$ref": "#/components/schemas/Evaluation" },
          "error": { "type": "string", "description": "Set when no usable record was found.", "example": "no SPF record found for \"example.com\"" }
        }
      },
      "Term": {
        "type": "object",
        "properties": {
          "raw": { "type": "string", "example": "ip4:192.0.2.0/24" },
          "qualifier": { "type": "string", "enum": ["+", "-", "~", "?"], "example": "+" },
          "kind": { "type": "string", "example": "ip4" },
          "value": { "type": "string", "example": "192.0.2.0/24" }
        }
      },
      "Evaluation": {
        "type": "object",
        "properties": {
          "result": { "type": "string", "enum": ["pass", "fail", "softfail", "neutral", "none", "permerror", "temperror"], "example": "pass" },
          "matched": { "type": "string", "description": "The term that decided the result.", "example": "ip4:192.0.2.0/24" },
          "explanation": { "type": "string", "example": "the IP is authorised to send mail for this domain" },
          "dns_lookups": { "type": "integer", "description": "DNS-querying mechanisms consumed (limit 10).", "example": 0 },
          "trace": { "type": "array", "items": { "type": "string" } }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "missing 'domain' parameter" }
        }
      }
    }
  }
}
