From competitor URL to content plan in 5 minutes
Drop a competitor URL, get back an 8–12-item prioritized content plan targeting their winnable keywords, content gaps, and missing angles — ready to feed into bulk blog production.
Drop a competitor URL. Get back a content plan that targets the keywords they rank for, the topics they cover that you don't, and the angles they're missing.
| Outcome | A 8–12-item content plan with prioritized briefs, ready to feed into produce.blog_post.bulk |
| Time | ~5 min to run; ~3 hours to draft the content from it |
| Cost | ~80 credits for the analysis + ~25 per blog post produced |
| Prereqs | Competitor's domain, your workspace ICPs + keywords already populated |
What it does
1. research.competitor.add (the competitor's domain)
2. research.competitor.keywords (pull what they rank for)
3. research.competitor.backlinks (pull their backlinks)
4. research.competitor.overlap (vs your keyword library)
5. research.content_gap (which competitor topics you don't cover)
6. research.serp_gap.bulk (winnability per gap keyword)
7. produce.blog_post (planning mode) over the winnable EASY_WIN + WINNABLE_WITH_EFFORT gapsStep 1 — Add the competitor
COMP=$(curl -sf -X POST .../v1/research/competitor \
-H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
-H "X-Workspace-Id: $WS" \
-d '{
"domain": "monday.com",
"weight": "incumbent"
}' | jq -r '.id')Step 2 — Pull their intelligence
curl -X POST .../v1/research/competitor/$COMP/keywords -d '{"topPosition":20,"minVolume":100}'
curl -X POST .../v1/research/competitor/$COMP/backlinks -d '{"minDomainRating":30,"linkType":"dofollow"}'Both return invocation handles; wait for SUCCEEDED.
Step 3 — Get the overlap matrix
curl .../v1/research/competitor/$COMP/overlapReturns summary.shared, summary.competitorOnly, summary.ourOnly, plus topGaps (keywords they rank for that you don't) and topWins (the opposite).
Step 4 — Content-gap analysis across multiple competitors
curl -X POST .../v1/research/content-gap -d "{
\"competitorIds\": [\"$COMP\"],
\"options\": {
\"includeWeakerCoverage\": true,
\"maxGapsPerType\": 25,
\"minKeywordVolume\": 100
}
}"When complete, fetch the report:
curl .../v1/research/content-gap/gap_***Returns ranked gaps with type: MISSING_TOPIC | KEYWORD_GAP | WEAKER_COVERAGE, recommended angles, estimated traffic lift.
Step 5 — Filter gaps by winnability
# Get keyword IDs for KEYWORD_GAP items
GAP_KW_IDS=$(curl -sf .../v1/research/content-gap/gap_*** \
| jq -r '.result.gaps[] | select(.type=="KEYWORD_GAP") | .keyword' \
| head -25 \
| while read KW; do
# Look up or create the keyword
curl -sf -X POST .../v1/keywords -d "{\"keywords\":[{\"keyword\":\"$KW\"}]}" \
| jq -r '.created[0].id'
done | jq -Rsc 'split("\n")[:-1]')
# Run winnability check
curl -X POST .../v1/research/serp-gap/bulk -d "{
\"keywordIds\": $GAP_KW_IDS
}"Filter to verdict ∈ {EASY_WIN, WINNABLE_WITH_EFFORT}.
Step 6 — Queue content from the winnable gaps
curl -X POST .../v1/research/content-gap/gap_***/queue-content -d '{
"selectGaps": [
{ "type": "MISSING_TOPIC", "priority": ["HIGH"] },
{ "type": "WEAKER_COVERAGE", "priority": ["HIGH"] }
],
"blogPostDefaults": {
"pillarSlug": "performance",
"mode": "with-research",
"refinerIds": ["rfn_brand-voice", "rfn_seo-cleanup"]
}
}'Returns a batch of queued blog posts at WAITING_APPROVAL.
One-shot script
#!/usr/bin/env bash
set -euo pipefail
KEY="${CITATIONBENCH_API_KEY:?}"
WS="${WORKSPACE_ID:?}"
COMPETITOR_DOMAIN="${1:?usage: $0 <competitor-domain>}"
BASE="https://api.citationbench.com/v1"
# 1. Add + pull
COMP=$(curl -sf -X POST $BASE/research/competitor \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d "{\"domain\":\"$COMPETITOR_DOMAIN\"}" | jq -r '.id')
curl -sf -X POST $BASE/research/competitor/$COMP/keywords \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS"
curl -sf -X POST $BASE/research/competitor/$COMP/backlinks \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS"
# (allow time for completion)
sleep 90
# 2. Content gap
GAP=$(curl -sf -X POST $BASE/research/content-gap \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d "{\"competitorIds\":[\"$COMP\"]}" | jq -r '.invocationId')
# 3. Wait
while true; do
S=$(curl -sf $BASE/agent/invocations/$GAP -H "Authorization: Bearer $KEY" | jq -r '.status')
[[ "$S" == "SUCCEEDED" ]] && break
[[ "$S" =~ ^(FAILED|CANCELLED)$ ]] && { echo "Gap analysis $S"; exit 1; }
sleep 30
done
REPORT_ID=$(curl -sf $BASE/agent/invocations/$GAP -H "Authorization: Bearer $KEY" \
| jq -r '.result.reportId')
# 4. Queue HIGH-priority content
curl -sf -X POST $BASE/research/content-gap/$REPORT_ID/queue-content \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d '{
"selectGaps": [
{ "type": "MISSING_TOPIC", "priority": ["HIGH"] },
{ "type": "WEAKER_COVERAGE", "priority": ["HIGH"] }
],
"blogPostDefaults": { "mode": "with-research", "refinerIds": ["rfn_brand-voice"] }
}'
echo "Done. Content drafts pending in your queue."Gotchas
- Competitor analysis is per-competitor. The deeper insight comes from analyzing 3+ competitors simultaneously. Add multiple, then content-gap across all.
- Wait between pulls. Backlink + keyword pulls are async; running content-gap before they finish returns partial data.
- Pillar selection matters. The default pillar shapes how the agent drafts. If your content plan spans pillars, queue content with per-gap pillar overrides.
- Quality over volume. 8–12 well-targeted gap pieces beat 50 broad ones. Filter aggressively to high-priority gaps.
Related
- API: Research · competitor
- API: Research · content gap
- API: Research · serp gap analysis
- API: Production · blog post
- Playbook: Keyword research for a brand in 20 minutes
Client-approval publishing
Gate every publish and outbound action behind explicit client approval — drafts move at agency speed, publishes wait for client trust, all powered by durable approval primitives.
Connect GSC + Ahrefs + DataForSEO
Step-by-step setup for the three core data integrations every SEO workspace needs, with the trade-offs of bring-your-own-key versus platform-provided credentials.