Introduction
SPF mistakes often stay invisible until legitimate mail starts landing in spam or an old vendor remains authorized to send on your domain. A missing policy, duplicate SPF records, or a permissive +all rule can turn a small DNS oversight into a deliverability and spoofing problem.
WhoisJSON does not expose a separate SPF-only endpoint. Use the DNS Lookup API /nslookup endpoint, read the TXT array, and select values beginning with v=spf1. The result is structured JSON that your application can inventory, parse, compare, and monitor.
What Is an SPF Record?
Sender Policy Framework, or SPF, is an email authentication policy published as a DNS TXT record. It describes which hosts or delegated services are allowed to send mail using a domain in the SMTP envelope sender.
example.com. 3600 IN TXT
"v=spf1 include:_spf.google.com ip4:192.0.2.0/24 -all"The record starts with v=spf1, followed by mechanisms and modifiers. Receiving mail systems evaluate those terms during SPF authentication. The DNS response itself gives you the published policy; complete SPF evaluation requires additional recursive DNS resolution and SMTP context.
The protocol behavior is defined in RFC 7208, including record selection, qualifiers, modifiers, and DNS lookup limits.
SPF Lookup API vs SPF Validation API
The distinction matters because a TXT lookup and a complete SPF validator answer different questions.
| Workflow | What it does | WhoisJSON scope |
|---|---|---|
| SPF record lookup | Retrieves root-domain TXT records and identifies values beginning with v=spf1. | Supported through /nslookup. |
| First-level parsing | Splits the published policy into mechanisms such as include, ip4, ip6, a, mx, redirect, and all. | Performed client-side using examples in this guide. |
| Full SPF validation | Recursively resolves includes and redirects, counts DNS-triggering terms, expands macros, and evaluates a sender IP. | Not returned by the documented endpoint. |
Query SPF TXT Records in JSON
Authenticate with Authorization: TOKEN=YOUR_API_KEY and query the root domain through /nslookup.
curl "https://whoisjson.com/api/v1/nslookup?domain=example.com" \
-H "Authorization: TOKEN=YOUR_API_KEY"The documented response exposes TXT as an array of strings. SPF is one possible use of TXT, so filter the array instead of treating every TXT value as sender policy.
{
"TXT": [
"google-site-verification=abc123",
"v=spf1 include:_spf.google.com ip4:192.0.2.0/24 -all"
],
"MX": [
{
"exchange": "mail.example.com",
"priority": 10
}
],
"DMARC": [
"v=DMARC1; p=reject"
]
}
How to Interpret Common SPF Mechanisms
A first-level parser can make the raw policy easier to review without claiming to perform complete SPF evaluation.
| Term | Meaning | Review question |
|---|---|---|
include:provider.example | Delegates authorization to another domain's SPF policy. | Is this provider still approved to send mail? |
ip4:192.0.2.0/24 | Authorizes an IPv4 address or range. | Is the range expected and appropriately narrow? |
ip6:2001:db8::/32 | Authorizes an IPv6 address or range. | Does the organization actually send from this range? |
a | Uses the domain's address records during SPF evaluation. | Should web-hosting addresses also send email? |
mx | Uses the domain's MX hosts during evaluation. | Are inbound mail servers also intended senders? |
redirect=policy.example | Redirects evaluation to another domain's policy. | Is the delegated policy controlled and current? |
-all | Hard fail for senders not matched earlier. | Is the authorized sender inventory complete? |
~all | Soft fail for unmatched senders. | Is this a migration state or the intended long-term policy? |
+all | Explicitly allows every sender. | Usually a critical misconfiguration. |
Qualifiers can also appear before mechanisms: + means pass, - means fail, ~ means soft fail, and ? means neutral. The absence of an explicit qualifier is equivalent to +.
Python Example: Find and Parse SPF
This example finds SPF-like TXT values and extracts first-level terms. It deliberately does not resolve include or redirect targets.
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://whoisjson.com/api/v1"
def parse_spf(record: str) -> dict:
terms = record.split()
return {
"record": record,
"terms": terms[1:],
"includes": [
term.split(":", 1)[1]
for term in terms
if term.lstrip("+~-?").startswith("include:")
],
"redirect": next(
(
term.split("=", 1)[1]
for term in terms
if term.startswith("redirect=")
),
None,
),
"all": next(
(
term for term in terms
if term.lstrip("+~-?") == "all"
),
None,
),
}
def lookup_spf(domain: str) -> dict:
response = requests.get(
f"{BASE_URL}/nslookup",
headers={"Authorization": f"TOKEN={API_KEY}"},
params={"domain": domain},
timeout=10,
)
response.raise_for_status()
txt_records = response.json().get("TXT") or []
spf_records = [
str(value).strip()
for value in txt_records
if str(value).strip().lower().startswith("v=spf1")
]
return {
"domain": domain,
"status": (
"missing" if not spf_records
else "duplicate" if len(spf_records) > 1
else "present"
),
"records": [parse_spf(record) for record in spf_records],
}
print(lookup_spf("example.com"))
Node.js Example: Flag Basic SPF Risks
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://whoisjson.com/api/v1";
async function lookupSpf(domain) {
const url = new URL(`${BASE_URL}/nslookup`);
url.searchParams.set("domain", domain);
const response = await fetch(url, {
headers: { Authorization: `TOKEN=${API_KEY}` },
});
if (!response.ok) {
throw new Error(`DNS lookup failed: ${response.status}`);
}
const data = await response.json();
const records = (data.TXT ?? [])
.map(String)
.map((value) => value.trim())
.filter((value) =>
value.toLowerCase().startsWith("v=spf1")
);
return {
domain,
status:
records.length === 0
? "missing"
: records.length > 1
? "duplicate"
: "present",
records,
findings: [
...(records.some((value) =>
/(^|\s)\+all(\s|$)/i.test(value)
)
? ["policy allows every sender with +all"]
: []),
...(records.some((value) =>
/(^|\s)~all(\s|$)/i.test(value)
)
? ["policy ends in soft fail (~all)"]
: []),
],
};
}
lookupSpf("example.com")
.then(console.log)
.catch(console.error);
Common SPF Findings
Missing SPF
No root TXT value begins with v=spf1. Confirm whether the domain sends email before assigning severity.
Multiple SPF records
More than one v=spf1 policy creates ambiguity and should be consolidated into one record.
Stale include
A former mail or marketing provider remains delegated after the service was removed.
Permissive all
+all authorizes every sender. ~all is less strict than -all and may be intentional during rollout.
v=spf1 -all to state that no sender is authorized.SPF vs MX vs DKIM vs DMARC
| Control | Main question | WhoisJSON data |
|---|---|---|
| MX | Where does the domain receive incoming email? | MX exchange and priority through /nslookup. |
| SPF | Which infrastructure may send using the envelope domain? | Raw root-domain TXT records; filter for v=spf1. |
| DKIM | Was the message signed by an authorized selector? | No dedicated selector-discovery endpoint. |
| DMARC | What should receivers do when aligned authentication fails? | DMARC policy through /nslookup. |
For inbound routing, use the MX record lookup guide. For policy enforcement and alignment context, use the DMARC lookup API guide.
Bulk SPF Audits for Domain Portfolios
One /nslookup request per domain returns TXT together with MX, DMARC, MTA-STS, TLS-RPT, and other DNS records. That makes a scheduled SPF inventory useful for provider migrations, vendor reviews, customer-domain onboarding, and domain portfolio governance.
- Store the exact SPF string and timestamp so policy changes are reviewable.
- Flag missing and duplicate records separately; they are different operational failures.
- Compare include and redirect targets with an approved provider inventory.
- Treat +all as urgent and ~all as a review item, not automatic proof of compromise.
- Use bounded concurrency and retry only transient API failures, not valid empty TXT results.
For production retry patterns, see the rate limits and retries guide.
What This SPF Lookup Cannot Prove
FAQ
What is an SPF record lookup API?
It retrieves DNS TXT records for a domain and identifies the sender policy value that begins with v=spf1.
Which WhoisJSON endpoint returns SPF?
Use GET /api/v1/nslookup with the domain parameter, then filter the TXT array for values beginning with v=spf1.
Does WhoisJSON fully validate SPF?
No. The documented endpoint returns TXT records. Recursive include resolution, DNS lookup counting, macro expansion, and sender-IP evaluation are outside this lookup workflow.
What is the difference between -all and ~all?
-all is a hard-fail qualifier for unmatched senders, while ~all is a soft-fail qualifier commonly used during gradual deployment.
Can I query SPF, MX, and DMARC together?
Yes. One /nslookup response can include TXT, MX, DMARC, BIMI, MTASTS, TLSRPT, and standard DNS records.
Conclusion
An SPF record lookup API workflow turns a raw DNS sender policy into data your application can inventory and review. Query /nslookup, filter TXT values for v=spf1, and parse the first-level mechanisms that matter to your provider and security baseline.
Keep lookup and validation separate. WhoisJSON returns the published TXT policy; it does not recursively evaluate every include or decide whether a specific sending IP passes SPF. That boundary makes the result predictable and honest.
Check SPF TXT records with WhoisJSON
Query SPF, MX, DMARC, MTA-STS, TLS-RPT, and standard DNS records with one API key.
Check SPF RecordsView Documentation