example

Find out who you're actually up against

Competitors ranked by keyword overlap in the SERP archive, rather than who you assume they are.

Who you think you compete with and who shares your SERPs are usually two different lists. Three endpoints, five requests, one shortlist. Responses on this page use semrush.com as the example domain.

language
prerequisites

Export your key once.

$ setupcurl
export SEOFETCH_KEY=sof_live_…
# the scripts below use jq -- brew install jq / apt-get install jq
step 1

Ask the archive who ranks where you rank.

→ requestcurl
curl https://api.seofetch.com/v1/domains/competitors \
  -H "Authorization: Bearer $SEOFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "semrush.com", "engine": "google", "location": 2840, "language": "en", "device": "desktop", "limit": 25}' \
  -o competitors.json
← response200
{
  "id": "doma_5vtgytujnbkbpbjdh2bklahb",
  "request_id": "req_6wypstdm7rocfc4krjiidkdpym",
  "object": "domain_competitors",
  "created_at": "2026-07-29T12:00:00Z",
  "elapsed_ms": 244,
  "credits": {
    "charged": 30,
    "balance": 9857
  },
  "data": {
    "domain": "semrush.com",
    "keywords_total": 2834,
    "keywords_considered": 200,
    "total_competitors": 1743,
    "competitors": [
      {
        "domain": "reddit.com",
        "shared_keywords": 106,
        "keywords_total": 114768,
        "avg_rank": 3.9,
        "target_avg_rank": 1,
        "overlap": 0.53,
        "shared_volume": 370910,
        "sample_keywords": [
          {
            "keyword": "google search console",
            "volume": 368000
          },
          "…"
        ]
      },
      "…"
    ],
    "coverage": {
      "from": "2026-05-20",
      "observations": 200
    }
  }
}

Ranked by overlap: shared keywords, weighted by how often you co-occur. Expect a giant or two at the top. reddit.com intersects everyone, and pruning them is your first edit. Built from the domain's most recent archived keywords; keywords_considered is the sample size.

step 2

Prune the giants; keep the 3-5 with real overlap.

$ localrun locally
jq -c '[.data.competitors[] | select(.overlap >= 0.3) | select(.domain as $d | ["reddit.com", "wikipedia.org", "quora.com"] | index($d) | not)]' competitors.json

The blocklist is yours; you know which platforms aren't competing with you. The 0.3 floor is a default too. Adjust both for your market.

step 3

Size up each survivor.

→ requestcurl
curl https://api.seofetch.com/v1/domains/overview \
  -H "Authorization: Bearer $SEOFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "ahrefs.com", "location": 2840, "language": "en"}'
← response200
{
  "id": "doma_l5sttkwwhbltpdaj4ocitm6l",
  "request_id": "req_vglyeybggrlz7kagqfagvj4kiy",
  "object": "domain_overview",
  "created_at": "2026-07-29T12:00:00Z",
  "elapsed_ms": 244,
  "credits": {
    "charged": 10,
    "balance": 9857
  },
  "data": {
    "organic": {
      "keywords_count": 101,
      "traffic_estimate": 70.17,
      "traffic_value": 704.38,
      "positions": {
        "top_3": 0,
        "top_10": 3,
        "top_20": 11,
        "top_100": 101
      }
    },
    "paid": {
      "keywords_count": 0,
      "traffic_estimate": 0,
      "traffic_value": 0,
      "positions": {
        "top_3": 0,
        "top_10": 0,
        "top_20": 0,
        "top_100": 0
      }
    }
  }
}

Keyword count, traffic estimate, position spread. 10 credits per competitor. Small overlap plus a big footprint is an aspiration, not a rival.

step 4

Turn the closest one into a keyword list.

→ requestcurl
curl https://api.seofetch.com/v1/domains/gap \
  -H "Authorization: Bearer $SEOFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["semrush.com", "ahrefs.com"], "mode": "gap", "engine": "google", "location": 2840, "language": "en", "device": "desktop", "limit": 100, "offset": 0}'
← response200
{
  "id": "doma_6ulpwpudmvigzhrj4wf4jm2k",
  "request_id": "req_5xqa6h3lmbmv7d3zqj55xkrzlu",
  "object": "domain_gap",
  "created_at": "2026-07-29T12:00:00Z",
  "elapsed_ms": 244,
  "credits": {
    "charged": 15,
    "balance": 9857
  },
  "data": {
    "mode": "gap",
    "domains": [
      "semrush.com",
      "ahrefs.com"
    ],
    "items": [
      {
        "keyword": "search engine optimization",
        "volume": 18100,
        "ranks": [
          null,
          {
            "rank": 2,
            "url": "https://ahrefs.com/blog/what-is-seo/",
            "date": "2026-06-28"
          }
        ]
      },
      "…"
    ],
    "totals": [
      2834,
      1315
    ],
    "matched_count": 409,
    "coverage": {
      "from": "2026-06-28",
      "observations": 409
    },
    "limit": 100,
    "offset": 0
  }
}

mode "gap" returns keywords they rank for that you don't. The content-gaps walkthrough picks up from there.

script

Run the whole thing.

Each language below is the full pipeline. Swap in your domain, export SEOFETCH_KEY, run it. The survivors land in cohort.csv, best footprint first, with a gap pull against the closest one waiting in gap.json.

→ runcompetitors.sh
#!/usr/bin/env bash
# competitors.sh -- who ranks where you rank -> pruned cohort -> gap vs the closest one.
set -euo pipefail
: "${SEOFETCH_KEY:?export SEOFETCH_KEY first}"
DOMAIN="${TARGET_DOMAIN:-yourdomain.com}"
api() { curl -sS --fail-with-body "https://api.seofetch.com$1" \
  -H "Authorization: Bearer $SEOFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d "$2"; }

# 1. who ranks where you rank
api /v1/domains/competitors \
  "$(jq -cn --arg d "$DOMAIN" '{domain: $d, engine: "google", location: 2840, language: "en", device: "desktop", limit: 25}')" \
  > competitors.json

# 2. prune giants, keep real overlap (0.3 floor -- policy knob)
jq -c '[.data.competitors[] | select(.overlap >= 0.3) | select(.domain as $d | ["reddit.com","wikipedia.org","quora.com"] | index($d) | not)]' \
  competitors.json > cohort.json
[ "$(jq length cohort.json)" -gt 0 ] || { echo "nothing survives the prune -- lower the floor or widen the blocklist"; exit 0; }

# 3. size up the top 3 survivors by overlap -- best-matched cohort first
SURVIVORS=3   # size up more: +10cr each
top_survivors=$(jq -c "sort_by(-.overlap) | .[0:${SURVIVORS}]" cohort.json)
: > cohort.csv
echo "$top_survivors" | jq -r '.[].domain' | while IFS= read -r d; do
  api /v1/domains/overview \
    "$(jq -cn --arg d "$d" '{domain: $d, location: 2840, language: "en"}')" > "overview-$d.json"
  jq -r --arg d "$d" \
    '[$d, (.data.organic.keywords_count // 0), (.data.organic.traffic_estimate // 0)] | @csv' \
    "overview-$d.json" >> cohort.csv
done
sort -t, -k3 -rn cohort.csv -o cohort.csv
echo "cohort.csv: domain, organic keywords, traffic estimate -- best first (footprint, not overlap)"

# 4. turn the highest-overlap survivor into a keyword list
top=$(echo "$top_survivors" | jq -r 'sort_by(-.overlap) | .[0].domain')
api /v1/domains/gap \
  "$(jq -cn --arg a "$DOMAIN" --arg b "$top" '{domains: [$a, $b], mode: "gap", engine: "google", location: 2840, language: "en", device: "desktop", limit: 100, offset: 0}')" \
  > gap.json
echo "gap.json: $(jq '.data.matched_count' gap.json) keywords $top ranks for that $DOMAIN doesn't"

Got a shortlist? The content-gaps walkthrough turns your closest competitor into a keyword list.