API REFERENCE · GET · STABLE

SPF Record API — /api/v1/domain/spf

Returns the raw SPF string, parsed mechanisms / qualifiers / directives, optional include expansion, RFC 7208 compliance checks (with `tests=true`), and — when analysis is cached — an AI review with severity-ranked issues plus quick recommendations. Client-side helpers (`spf-analytics.js`) derive policy strength, health score, lookup / void counts, sender inventory, provider fingerprint and a sender-IP simulator from the same response.

01 · OVERVIEW

What SPF Record does

Returns the raw SPF string, parsed mechanisms / qualifiers / directives, optional recursive include expansion, RFC 7208 compliance checks (with `tests=true`), and AI-generated findings. The response is the ground truth; the UI computes derived analytics (health score, policy strength, 10-lookup / 2-void meters, sender inventory, provider fingerprint, sender-IP simulator) on the client using `src/features/domain/SpfLookup/spf-analytics.js`.

  • Method GET
  • Path /api/v1/domain/spf
  • Stability STABLE
  • Version v2026-05-15
  • Summary Fetch and parse the SPF record for a domain.
02 · PARAMETERS

Request parameters

Pass these as query-string parameters (or, where indicated, in the request headers). Required params marked required.

  • domain (required) — string Apex domain.
  • tests — boolean, default false Run additional best-practice checks (DNS lookup count, void lookups, syntax).
  • expand — boolean, default false Recursively expand `include:` references and resolve `ip4` / `ip6` mechanisms.
03 · RESPONSE

Response schema

All successful responses follow the standard `{ success: true, data: {...} }` envelope. Notable fields on the data object for this endpoint:

  • data.spf — string Raw SPF TXT record (v=spf1 …).
  • data.parsed.version — string Always `v=spf1` for valid records.
  • data.parsed.mechanisms — object Buckets for `ip4`, `ip6`, `include`, `a`, `mx`, etc. plus qualifier + directives arrays.
  • data.parsed.qualifier — string Terminal `all` qualifier: `-all`, `~all`, `?all`, or `+all`.
  • data.tests — array<object> RFC 7208 compliance checks. Empty unless `tests=true`. Each item: `{ test, result, passed }`. Includes: Multiple SPF Records, Too Many DNS Lookups (with `N/10` in the result), SPF Policy Strength, No 'PTR' Mechanism, SPF Record Length, Number of Void Lookups, SPF record validity, No Deprecated Mechanisms.
  • data.expanded — object Populated only with `expand=true`. Recursive tree: `{ domain, spfRecord, lookupCount, ip4List, ip6List, mechanisms, includes: [{ domain, expanded: {...} }] }`. Feeds the client-side simulator + inventory.
  • data.AiAnalysis — object | null Present only when the AI review is cached (cache hit). Shape: `{ issues: [{ severity, title, description, recommendation }], quick_recommendations: [string] }`. Severity is one of `info | warning | critical`.
  • data.AiAnalysisMeta — object | null Present on cache miss instead of `AiAnalysis`. Shape: `{ id, avgMs }`. Poll `/ai/analysis/:id` (via `useAiAnalysisPoll`) until the review completes, then merge the resulting object into `AiAnalysis`.
04 · EXAMPLE

Example response

Truncated but representative example for a real lookup:

Sample response
{
  "success": true,
  "data": {
    "spf": "v=spf1 include:_spf.google.com ~all",
    "parsed": {
      "version": "v=spf1",
      "mechanisms": { "ip4": [], "include": ["_spf.google.com"] },
      "modifiers": [],
      "qualifier": "~all",
      "directives": ["include:_spf.google.com", "~all"]
    },
    "tests": [
      { "test": "Multiple SPF Records",     "result": "Only one SPF record found.",           "passed": true },
      { "test": "Too Many DNS Lookups",     "result": "DNS lookups within limit (4/10).",     "passed": true },
      { "test": "SPF Policy Strength",      "result": "SoftFail (~all) policy used.",         "passed": true },
      { "test": "No 'PTR' Mechanism",       "result": "No type 'PTR' found.",                 "passed": true },
      { "test": "SPF Record Length",        "result": "String length is OK.",                 "passed": true },
      { "test": "Number of Void Lookups",   "result": "Number of void lookups is OK. (0)",    "passed": true },
      { "test": "SPF record validity",      "result": "Valid",                                "passed": true },
      { "test": "No Deprecated Mechanisms", "result": "No deprecated records found.",         "passed": true }
    ],
    "expanded": {
      "domain": "example.com",
      "spfRecord": "v=spf1 include:_spf.google.com ~all",
      "lookupCount": 4,
      "ip4List": [],
      "ip6List": [],
      "mechanisms": [
        { "type": "include", "qualifier": "+", "value": "_spf.google.com" },
        { "type": "all",     "qualifier": "~", "value": null }
      ],
      "includes": [
        { "domain": "_spf.google.com", "expanded": { "...": "recursive" } }
      ]
    },
    "AiAnalysisMeta": { "id": "a1b2c3", "avgMs": 4200 }
  }
}
05 · ERRORS

Error codes

Errors return HTTP status codes paired with stable machine-readable codes. Match on the code field rather than the human-readable message.

  • 404 NO_SPF_RECORD Domain has no SPF TXT record.
06 · BEST PRACTICES

Implementation tips

Patterns we recommend when integrating this endpoint:

  • Pass `tests=true` for production audits — catches >10 lookups, void lookups, deprecated mechanisms, policy strength.
  • Use `expand=true` when you need the resolved chain (required by sender inventory, provider fingerprint, IP simulator). Adds latency proportional to chain depth; safe to cache for hours.
  • First-request `AiAnalysisMeta` returned instead of `AiAnalysis` — poll the analysis endpoint or reload after ~5s for the cached result.
  • Compute derived analytics (health score, policy strength, lookup / void meters, sender inventory, provider fingerprint, sender-IP simulator) client-side via `spf-analytics.js`. Keeps the API stateless and lets you re-derive without a network round-trip.
07 · FAQ

Frequently asked questions

  • What does the qualifier `~all` mean? Soft-fail: mail from unauthorized senders is marked but not rejected. `-all` is the strict equivalent.
  • Why did I get `AiAnalysisMeta` instead of `AiAnalysis`? First request for a given domain — the analyzer runs in the background. Poll the meta id or refresh after ~5s and you'll get the cached `AiAnalysis` object. Meta is auto-included; you don't need to opt in.
  • How do I evaluate a specific sender IP against this SPF? Call the endpoint with `expand=true`, then pass `data.expanded` and the IP to `simulateSender()` in `spf-analytics.js`. It walks the resolved chain client-side (no extra DNS lookups) and returns `{ result, matchedBy, matchedIn, chain }`. Full IPv4 + IPv6 CIDR matching.
  • Where does the health score come from? Client-side. `computeHealth({ tests, lookupCount, voidCount, policy })` weights the compliance-test pass ratio, penalises lookup pressure (hard cliff at 10), void-lookup pressure (hard cap at 2) and weak policy (`+all` heavy penalty, `?all`/missing lesser). Returns `{ score, band, tone }`.