Keyword research for a brand in 20 minutes with bootstrap_brand
Hand a domain to the bootstrap_brand agent and get back ICP, 800–1,500 labeled keywords, competitor map, Reddit pain points, a 12-week content plan, and 6 landing-page briefs.
The headline CitationBench workflow. Hand a URL to the bootstrap_brand agent. In under 25 minutes you get back: ICP, full keyword universe with 2D labels, competitor map, Reddit pain points, a 12-week content plan, and 6 landing-page briefs.
This used to take a senior strategist 3–5 days. It now runs as a single agent invocation, with optional human approval at every step.
| Outcome | New workspace with ICP segments, 800–1,500 labeled keywords, 8 mapped competitors, 20+ scored pain points, 12-week content plan, 6 landing-page briefs |
| Time | ~20 min (depth: thorough) · ~10 min (depth: fast) |
| Cost | ~150 credits |
| Prereqs | CitationBench API key, target domain, DataForSEO + Ahrefs + Reddit integrations on the workspace (BYOK supported) |
What the agent actually does
bootstrap_brand chains seven CitationBench endpoints in a parent–child invocation graph. Each step is durable, idempotent, and (optionally) approvable.
agent.invoke(agent: bootstrap_brand)
├── produce.crawl (~1 min) — crawl + embed the domain
├── research.icp (~2 min) — 3 customer segments
├── research.keyword (~5 min) — 800–1,500 keywords, 2D-labeled
├── research.competitor (~3 min) — 8 competitors, overlap matrix
├── research.discuss (~4 min) — 20–30 scored Reddit pain points
├── produce.blog_post (planning mode) (~2 min) — 12-week content plan
└── produce.landing_page (briefs) (~3 min) — 6 ready-to-write briefsEvery child is a full agent invocation with its own ID, replay log, and approval gates. Every output persists into your new workspace and is queryable via the API after the run completes.
→ Concept: Agent → API: Agent · invoke → API: Research · keyword
Step 1 — Invoke the agent
REST
curl -X POST https://api.citationbench.com/v1/agent/invoke \
-H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"skill": "bootstrap_brand",
"input": {
"domain": "acme.com",
"depth": "thorough",
"workspace": "auto",
"options": {
"competitorCount": 8,
"keywordTarget": 1000,
"contentPlanWeeks": 12,
"landingPageBriefs": 6,
"customInstructions": "Focus on enterprise buyers in fintech."
}
},
"approval": { "required": true, "scope": "every_step" },
"mode": "BACKGROUND",
"tags": ["onboarding", "client:acme"]
}'MCP (Claude Code)
> Bootstrap acme.com — full SEO and GEO research, focused on enterprise fintech buyers.
Pause at each step so I can approve.Response
{
"invocationId": "inv_01HVZRJZ3T8M7E0B0V0Z6KQ3A4",
"agentId": "agt_01HVZRJZ3T8M7E0B0V0Z6KQ3A5",
"skill": "bootstrap_brand",
"status": "PENDING",
"estimatedCost": { "credits": 152, "durationSeconds": 1200 },
"workspace": "ws_acme",
"links": {
"self": "https://api.citationbench.com/v1/agent/invocations/inv_***",
"events": "https://api.citationbench.com/v1/agent/invocations/inv_***/events",
"approvals": "https://api.citationbench.com/v1/agent/approvals?invocationId=inv_***"
}
}workspace: "auto" created ws_acme from the domain. Pass an explicit workspace if you've bootstrapped this domain before.
Step 2 — Watch the run
INV=inv_01HVZ...
curl -N -H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
"https://api.citationbench.com/v1/agent/invocations/$INV/events"Event stream:
event: step.started
data: { "step": "crawl", "tool": "produce.crawl", "input": { "domain": "acme.com" } }
event: step.completed
data: { "step": "crawl", "result": { "pagesCrawled": 14, "embeddedPages": 14 } }
event: step.started
data: { "step": "icp", "tool": "research.icp" }
event: step.completed
data: { "step": "icp", "result": { "segments": 3 } }
event: step.awaiting_approval
data: { "step": "keywords", "approvalId": "appr_***", "preview": { "totalKeywords": 1247, "byIntent": { "PURCHASE": 312, "PROBLEM_SOLUTION": 489 } } }
... (more)
event: status.changed
data: { "from": "RUNNING", "to": "SUCCEEDED", "creditsUsed": 147 }Or watch the dashboard at https://app.citationbench.com/jobs/$INV.
Step 3 — Approve / edit each step (if approval.required: true)
When the invocation reaches WAITING_APPROVAL:
List pending approvals
curl -H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
"https://api.citationbench.com/v1/agent/approvals?invocationId=$INV"Response:
{
"approvals": [
{
"approvalId": "appr_***",
"invocationId": "inv_***",
"step": "keywords",
"preview": {
"totalKeywords": 1247,
"byIntent": {
"PURCHASE": 312,
"PROBLEM_SOLUTION": 489,
"ALTERNATIVE": 198,
"OTHER": 248
},
"byPillar": { "performance": 312, "pricing": 289, "integrations": 198 },
"topKeywords": [
{
"id": "kw_***",
"keyword": "engineering team capacity tracking",
"kd": 22,
"volume": 480
},
{
"id": "kw_***",
"keyword": "project management for distributed engineering teams",
"kd": 31,
"volume": 320
}
]
},
"createdAt": "2026-05-24T08:09:18Z"
}
]
}Approve as-is
curl -X POST -H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
"https://api.citationbench.com/v1/agent/approvals/appr_***/approve"Approve with edits
curl -X POST -H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
"https://api.citationbench.com/v1/agent/approvals/appr_***/approve" \
-d '{
"edits": {
"dropKeywordIds": ["kw_AAA", "kw_BBB"],
"additionalSeeds": ["sprint planning for engineering"]
},
"note": "Drop the two outliers, add the sprint-planning seed."
}'Reject and re-run with different config
curl -X POST -H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
"https://api.citationbench.com/v1/agent/approvals/appr_***/reject" \
-d '{
"rerun": { "step": "keywords", "config": { "limit": 800 } },
"note": "Too many keywords for v1 plan — cap at 800."
}'Step 4 — Inspect the final result
curl -H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
"https://api.citationbench.com/v1/agent/invocations/$INV"When status: SUCCEEDED:
{
"invocationId": "inv_***",
"status": "SUCCEEDED",
"creditsUsed": 147,
"durationMs": 1124820,
"result": {
"workspaceId": "ws_acme",
"dashboardUrl": "https://app.citationbench.com/ws_acme",
"icp": {
"segments": [
{
"name": "Engineering leadership at fintech",
"jobToBeDone": "Ship faster without burning the team out",
"pain": "Capacity is opaque; tickets pile up; oncall is a tax",
"budgetShape": "$200-800/month per seat; quarterly buys",
"channels": [
"r/projectmanagement",
"Lenny's Newsletter",
"Hacker News"
]
}
]
},
"keywordUniverse": {
"total": 1247,
"byIntent": {
"PURCHASE": 312,
"PROBLEM_SOLUTION": 489,
"ALTERNATIVE": 198,
"OTHER": 248
},
"pillars": [
{ "id": "pil_pricing", "name": "Pricing", "count": 312 },
{ "id": "pil_performance", "name": "Performance", "count": 289 },
{ "id": "pil_integrations", "name": "Integrations", "count": 198 }
]
},
"competitors": [
{
"domain": "monday.com",
"overlap": 0.47,
"gap": 0.18,
"estimatedTraffic": 1240000
},
{
"domain": "asana.com",
"overlap": 0.43,
"gap": 0.22,
"estimatedTraffic": 980000
}
],
"painPoints": [
{
"topic": "Cross-team capacity tracking is invisible until it's too late",
"signalScore": 0.91,
"sources": [
"r/projectmanagement/comments/abc",
"r/engineeringmanagers/comments/xyz"
],
"verbatimQuotes": [
"We didn't know we were 30% over until the sprint review"
]
}
],
"contentPlan": {
"weeks": [
{
"week": 1,
"items": [
{
"type": "blog_post",
"title": "How engineering teams track capacity without slowing delivery",
"keywordId": "kw_***",
"pillarSlug": "performance"
},
{
"type": "landing_page",
"title": "Project management for engineering teams",
"keywordId": "kw_***",
"pillarSlug": "pricing"
}
]
}
]
},
"landingPageBriefs": [
{
"id": "brief_***",
"keywordId": "kw_***",
"pillarId": "pil_pricing",
"outlineUrl": "https://app.citationbench.com/briefs/brief_***"
}
]
}
}Step 5 — Use the outputs
Every output is persisted. Compose them with other CitationBench endpoints.
Generate the first 6 landing pages from the briefs
curl -sf "https://api.citationbench.com/v1/agent/invocations/$INV" \
| jq -r '.result.landingPageBriefs[].id' \
| while read BRIEF; do
curl -X POST .../v1/produce/landing-page \
-H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
-d "{ \"briefId\": \"$BRIEF\" }"
doneIn MCP:
> Generate landing pages for all 6 briefs from the bootstrap.Filter the keyword universe to start blog content
KW_IDS=$(curl -sf -X POST .../v1/keywords/search -d '{
"intent": ["PROBLEM_SOLUTION"],
"relevance": ["OFFERING"],
"minConfidence": 0.85,
"maxKd": 35,
"limit": 20
}' | jq -r '.data[].id' | jq -Rsc 'split("\n")[:-1]')
curl -X POST .../v1/produce/blog-post/bulk -d "{
\"keywordIds\": $KW_IDS,
\"pillarSlug\": \"performance\",
\"mode\": \"with-research\",
\"refinerIds\": [\"rfn_brand-voice\"]
}"Schedule rank tracking on the new keywords
curl -X POST .../v1/agent/invoke -d '{
"skill": "rank_monitor",
"input": {
"workspace": "ws_acme",
"scope": { "intent": ["PURCHASE", "ALTERNATIVE"] },
"schedule": "weekly:mon:09:00",
"alertOn": { "drop": 5 }
}
}'One-shot script (bootstrap to first published landing page)
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="acme.com"
KEY="${CITATIONBENCH_API_KEY:?set CITATIONBENCH_API_KEY}"
BASE="https://api.citationbench.com/v1"
# 1. Bootstrap (BACKGROUND mode — we'll poll)
INV=$(curl -sf -X POST $BASE/agent/invoke \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{
\"skill\": \"bootstrap_brand\",
\"input\": { \"domain\": \"$DOMAIN\", \"depth\": \"thorough\" },
\"mode\": \"BACKGROUND\"
}" | jq -r '.invocationId')
echo "Started invocation $INV"
# 2. Wait for completion
while true; do
STATUS=$(curl -sf -H "Authorization: Bearer $KEY" "$BASE/agent/invocations/$INV" | jq -r '.status')
case "$STATUS" in
SUCCEEDED) echo "Bootstrap done"; break ;;
FAILED|CANCELLED) echo "Bootstrap $STATUS"; exit 1 ;;
*) echo " ...$STATUS"; sleep 30 ;;
esac
done
# 3. Generate landing pages from the 6 briefs
curl -sf -H "Authorization: Bearer $KEY" "$BASE/agent/invocations/$INV" \
| jq -r '.result.landingPageBriefs[].id' \
| while read BRIEF; do
LP_INV=$(curl -sf -X POST $BASE/produce/landing-page \
-H "Authorization: Bearer $KEY" \
-d "{ \"briefId\": \"$BRIEF\" }" | jq -r '.invocationId')
echo " Landing page invocation: $LP_INV"
done
# 4. Schedule weekly rank monitoring on the new keywords
curl -sf -X POST $BASE/agent/invoke \
-H "Authorization: Bearer $KEY" \
-d "{
\"skill\": \"rank_monitor\",
\"input\": { \"workspace\": \"ws_${DOMAIN//./_}\", \"schedule\": \"weekly:mon:09:00\" }
}"
echo "Bootstrap → 6 landing pages → weekly rank monitor ready"Gotchas
- Domain must be live and crawlable. If the site is behind aggressive bot blocking,
produce.crawlfails fast. Passoptions.useCachedSnapshot: trueto use a stored crawl, oroptions.maxPagesto keep it small. - External rate limits matter. Ahrefs and DataForSEO have per-account rate limits. Heavy parallel bootstraps can throttle. Use BYOK API keys (
config.byok) for big agency cohorts. workspace: "auto"is greedy. It creates a new workspace named after the domain. If you've bootstrapped this domain before, pass an explicitworkspaceto avoid duplicates.- GEO citation tracking is off by default in bootstrap. Add
options.includeGeoCitations: trueto addresearch.ai-citationas an 8th step (adds ~5 min and ~10 credits). - Reddit research depends on subreddit availability. If
research.discussfinds nothing strong (rare topics, brand-new niches), the content plan leans more on competitor research. - Idempotency: re-running with the same
Idempotency-Keyreturns the original invocation instead of starting a new one. Useful for resumable scripts; don't include it if you genuinely want a fresh run.
Related
- Concept: Agent
- Concept: Cyclonic jobs
- Concept: Approval workflows
- API: Agent · invoke
- API: Research · keyword
- API: Research · icp
- API: Research · discuss
- API: Research · competitor
- API: Production · landing page
- Playbook: Generate 100 landing pages overnight
- Playbook: Client-approval gated publishing
- Playbook: From competitor URL to content plan
Custom client
Connect any compliant MCP client to the hosted CitationBench server over streamable HTTP. Standard tools/list and tools/call, bearer auth, per-call workspace scoping for custom agents.
Weekly rank checks
Set up one Monday-morning scheduled job that fans out rank checks across every workspace, alerts on meaningful drops, and optionally triggers a stale-content refresh on impacted pages.