Appearance
SOP: Backlink Audit Fresh
This SOP walks through a complete backlink audit for a domain: pulling the profile, identifying toxic links, analyzing anchor text, and summarizing referring domains.
What You're Measuring
| Metric | Why It Matters |
|---|---|
| Total backlinks | Raw link count — less meaningful than referring domains |
| Referring domains | Unique sites linking to you — the real authority signal |
| Referring domains (dofollow) | Links that pass PageRank |
| Broken backlinks | Links to 404 pages — lost equity |
| Toxic score | DataForSEO's spam rating (0–100, higher = worse) |
| Anchor text distribution | Over-optimized anchors are a spam signal |
| New vs. lost links | Velocity trend — is the profile growing or decaying? |
Step 1: Get Domain Summary
Start with the summary endpoint to get aggregate metrics before pulling individual links.
python
import os
import requests
from requests.auth import HTTPBasicAuth
auth = HTTPBasicAuth(
os.environ["DATAFORSEO_LOGIN"],
os.environ["DATAFORSEO_PASSWORD"]
)
target_domain = "example.com" # domain to audit (no https://)
payload = [
{
"target": target_domain,
"include_subdomains": True
}
]
response = requests.post(
"https://api.dataforseo.com/v3/backlinks/domain_pages_summary/live",
auth=auth,
json=payload
)
data = response.json()
task = data["tasks"][0]
result = task["result"][0]
print(f"Domain: {target_domain}")
print(f"Total backlinks: {result.get('backlinks', 0):,}")
print(f"Referring domains: {result.get('referring_domains', 0):,}")
print(f"Referring domains (do): {result.get('referring_domains_dofollow', 0):,}")
print(f"Referring IPs: {result.get('referring_ips', 0):,}")
print(f"Broken backlinks: {result.get('broken_backlinks', 0):,}")
print(f"Spam score (domain): {result.get('spam_score', 0)}")Step 2: Pull Referring Domains
Get the list of domains linking to your target, sorted by domain rank.
python
def get_referring_domains(auth, target, limit=100, offset=0):
"""Pull referring domains with key metrics."""
payload = [
{
"target": target,
"limit": limit,
"offset": offset,
"order_by": ["rank,desc"],
"include_subdomains": True,
"filters": [
["dofollow", "=", True] # dofollow only
]
}
]
response = requests.post(
"https://api.dataforseo.com/v3/backlinks/referring_domains/live",
auth=auth,
json=payload
)
data = response.json()
task = data["tasks"][0]
domains = []
for item in task["result"][0].get("items", []):
domains.append({
"domain": item["domain"],
"rank": item.get("rank", 0),
"backlinks": item.get("backlinks", 0),
"dofollow": item.get("dofollow", 0),
"first_seen": item.get("first_seen"),
"lost_date": item.get("lost_date"),
"spam_score": item.get("spam_score", 0)
})
return domains
referring_domains = get_referring_domains(auth, target_domain, limit=200)
print(f"\nReferring domains (dofollow): {len(referring_domains)}")Step 3: Identify Toxic Links
Flag referring domains with high spam scores for disavow consideration.
python
TOXIC_THRESHOLD = 60 # spam score 60+ is considered toxic
toxic_domains = [
d for d in referring_domains
if d["spam_score"] >= TOXIC_THRESHOLD
]
suspicious_domains = [
d for d in referring_domains
if 40 <= d["spam_score"] < TOXIC_THRESHOLD
]
print(f"\nToxic domains (score >= {TOXIC_THRESHOLD}): {len(toxic_domains)}")
print(f"Suspicious domains (score 40-59): {len(suspicious_domains)}")
if toxic_domains:
print("\nTop Toxic Domains:")
print(f"{'Domain':<40} {'Spam Score':>12} {'Backlinks':>10}")
print("-" * 65)
for d in sorted(toxic_domains, key=lambda x: x["spam_score"], reverse=True)[:20]:
print(f"{d['domain']:<40} {d['spam_score']:>12} {d['backlinks']:>10}")Step 4: Analyze Anchor Text Distribution
Over-optimized exact-match anchors are a red flag. Healthy profiles are mostly brand + URL anchors.
python
def get_anchor_text_distribution(auth, target, limit=100):
"""Get anchor text breakdown for the target domain."""
payload = [
{
"target": target,
"limit": limit,
"order_by": ["backlinks,desc"],
"include_subdomains": True
}
]
response = requests.post(
"https://api.dataforseo.com/v3/backlinks/anchors/live",
auth=auth,
json=payload
)
data = response.json()
task = data["tasks"][0]
anchors = []
for item in task["result"][0].get("items", []):
anchors.append({
"anchor": item["anchor"],
"backlinks": item.get("backlinks", 0),
"referring_domains": item.get("referring_domains", 0),
"dofollow": item.get("dofollow", 0)
})
return anchors
anchors = get_anchor_text_distribution(auth, target_domain)
# Calculate % of total
total_backlinks = sum(a["backlinks"] for a in anchors)
print("\nTop 20 Anchor Texts:")
print(f"{'Anchor':<50} {'Backlinks':>10} {'Share %':>8}")
print("-" * 72)
for a in anchors[:20]:
pct = (a["backlinks"] / total_backlinks * 100) if total_backlinks else 0
anchor_text = (a["anchor"] or "[blank]")[:49]
print(f"{anchor_text:<50} {a['backlinks']:>10,} {pct:>7.1f}%")Step 5: Check New and Lost Links
Track link velocity — rapid new link acquisition can be a spam signal; large link loss is a problem.
python
from datetime import datetime, timedelta
def get_new_lost_backlinks(auth, target, days_back=30):
"""Get links gained and lost in the past N days."""
date_from = (datetime.utcnow() - timedelta(days=days_back)).strftime("%Y-%m-%d")
# New links
new_payload = [
{
"target": target,
"date_from": date_from,
"limit": 50,
"order_by": ["first_seen,desc"]
}
]
new_response = requests.post(
"https://api.dataforseo.com/v3/backlinks/backlinks/live",
auth=auth,
json=new_payload
)
new_data = new_response.json()
new_links = new_data["tasks"][0]["result"][0].get("items", [])
return {
"new_links": len(new_links),
"new_samples": new_links[:10]
}
velocity = get_new_lost_backlinks(auth, target_domain, days_back=30)
print(f"\nNew backlinks in last 30 days: {velocity['new_links']}")Step 6: Generate Audit Summary
python
def print_audit_summary(domain, referring_domains, toxic_domains, suspicious_domains, anchors):
"""Print a clean audit summary."""
total_rd = len(referring_domains)
toxic_pct = (len(toxic_domains) / total_rd * 100) if total_rd else 0
avg_rank = sum(d["rank"] for d in referring_domains) / total_rd if total_rd else 0
print("\n" + "=" * 60)
print(f"BACKLINK AUDIT SUMMARY: {domain}")
print("=" * 60)
print(f"Referring domains analyzed: {total_rd:,}")
print(f"Average domain rank: {avg_rank:.0f}")
print(f"Toxic domains: {len(toxic_domains)} ({toxic_pct:.1f}%)")
print(f"Suspicious domains: {len(suspicious_domains)}")
print(f"Top anchor text: {anchors[0]['anchor'] if anchors else 'N/A'}")
print()
if toxic_pct > 10:
print("ACTION NEEDED: Toxic link ratio is high. Build disavow file.")
elif toxic_pct > 5:
print("CAUTION: Toxic link ratio is elevated. Monitor closely.")
else:
print("STATUS: Backlink profile looks healthy.")
print_audit_summary(target_domain, referring_domains, toxic_domains, suspicious_domains, anchors)Building a Disavow File
If toxic links are significant, export a disavow file in Google's format:
python
def export_disavow_file(toxic_domains, filepath="disavow.txt"):
"""Export toxic domains in Google Disavow Tool format."""
with open(filepath, "w") as f:
f.write("# Disavow file generated by DataForSEO backlink audit\n")
f.write(f"# Generated: {datetime.utcnow().strftime('%Y-%m-%d')}\n\n")
for d in sorted(toxic_domains, key=lambda x: x["spam_score"], reverse=True):
f.write(f"domain:{d['domain']}\n")
print(f"Disavow file saved: {filepath} ({len(toxic_domains)} domains)")
if len(toxic_domains) > 0:
export_disavow_file(toxic_domains)Healthy Profile Benchmarks
| Metric | Healthy | Concerning |
|---|---|---|
| Toxic domain ratio | < 5% | > 10% |
| Blank/URL anchors | > 40% of total | < 20% |
| Exact-match anchors | < 15% | > 30% |
| Do-follow ratio | 60–80% | < 40% or > 95% |
| Spam score (domain avg) | < 20 | > 40 |