CitationBenchTalk to Sales
Playbooks

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.

OutcomeNew 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
PrereqsCitationBench 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 briefs

Every 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\" }"
    done

In 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.crawl fails fast. Pass options.useCachedSnapshot: true to use a stored crawl, or options.maxPages to 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 explicit workspace to avoid duplicates.
  • GEO citation tracking is off by default in bootstrap. Add options.includeGeoCitations: true to add research.ai-citation as an 8th step (adds ~5 min and ~10 credits).
  • Reddit research depends on subreddit availability. If research.discuss finds nothing strong (rare topics, brand-new niches), the content plan leans more on competitor research.
  • Idempotency: re-running with the same Idempotency-Key returns the original invocation instead of starting a new one. Useful for resumable scripts; don't include it if you genuinely want a fresh run.

On this page