Generate 100 programmatic SEO landing pages overnight
Feed in a keyword list, get 100 brand-aligned landing pages back the next morning — programmatic SEO at scale using a 4-step pipeline with optional auto-publish and indexing.
A keyword list goes in. A hundred publish-ready landing pages come out. Programmatic SEO at scale, with the same 4-step pipeline (search intent → SEO metadata → page copy → final metadata) per page.
| Outcome | 100 brand-aligned landing pages, optionally auto-published and indexed |
| Time | ~30 sec to queue; ~6 hours to complete 100 at default concurrency |
| Cost | ~40 credits per page × 100 = ~4,000 credits |
| Prereqs | A pillar template configured, 100+ FOCUSED keywords ready, refiner set picked |
What it does
For each keyword in the bulk list:
produce.landing_page (with pillar template)
↓ runs the 4-step pipeline
↓ applies refiners
↓ optional: produce.publish
↓ optional: indexing.gsc.submit + indexing.indexnow
All running in parallel at the configured concurrency.Step 1 — Pick a pillar
Each landing page uses a pillar's template. Common pillars for programmatic SEO:
| Pillar | Use case |
|---|---|
comparison | "X vs Y" pages |
feature | Per-feature landing pages |
industry | Per-industry landing pages |
location | Per-city/region landing pages |
use-case | Per-use-case landing pages |
If you don't have one for this purpose:
curl -X POST .../v1/produce/landing-page/pillar -d '{
"slug": "industry",
"name": "Industry pages",
"promptTemplate":"Generate an industry-specific landing page targeting <industry>",
"schema": { "type": "object", "properties": { "hero": { ... }, "industryProofPoints": { ... }, ... } },
"baseUrl": "/industries/",
"defaultRefinerIds": ["rfn_brand-voice", "rfn_seo-cleanup", "rfn_og-image"]
}'Step 2 — Filter to the keywords you want pages for
KW_IDS=$(curl -sf -X POST .../v1/keywords/search \
-H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
-H "X-Workspace-Id: $WS" \
-d '{
"intent": ["PURCHASE", "SPECIFICATION", "LOCATION"],
"relevance": ["OFFERING"],
"minVolume": 50,
"maxKd": 45,
"missingFromContent": true,
"limit": 100
}' | jq -r '.data[].id' | jq -Rsc 'split("\n")[:-1]')
echo "Found $(echo $KW_IDS | jq 'length') keywords"Step 3 — Fire the bulk job
curl -X POST .../v1/produce/landing-page/bulk \
-H "Authorization: Bearer $CITATIONBENCH_API_KEY" \
-H "X-Workspace-Id: $WS" \
-d "{
\"keywordIds\": $KW_IDS,
\"pillarSlug\": \"industry\",
\"concurrency\": 4
}"Returns a batchId + one invocation per keyword.
Step 4 — Watch progress
curl .../v1/produce/landing-page \
-G --data-urlencode "batch=$BATCH" --data-urlencode "limit=100" \
| jq '.data | group_by(.generationStatus) | map({status: .[0].generationStatus, count: length})'Or wait for the batch-complete webhook:
curl -X POST .../v1/webhooks -d '{
"url": "https://hooks.our-portal.com/lp-batch",
"events": ["produce.landing_page.batch.completed"]
}'Step 5 — Optional: auto-publish + index
curl -X POST .../v1/agent/invoke -d '{
"skill": "agent.scheduled",
"input": {
"steps": [
{
"action": "produce.landing-page.list",
"config": { "batch": "<batchId>", "status": "DRAFT_READY" }
},
{
"action": "produce.publish",
"forEachFrom": "$.previousStep.data",
"config": {
"contentType": "landing_page",
"contentId": "{{ item.id }}",
"platformConfigId": "pfm_wisp-pricing",
"alsoIndex": true
}
}
]
}
}'Or approve them in the dashboard one at a time if you want eyes on each.
One-shot script
#!/usr/bin/env bash
set -euo pipefail
KEY="${CITATIONBENCH_API_KEY:?}"
WS="${WORKSPACE_ID:?}"
PILLAR="${1:-industry}"
LIMIT="${2:-100}"
BASE="https://api.citationbench.com/v1"
# 1. Find the keywords
KW_IDS=$(curl -sf -X POST $BASE/keywords/search \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d "{
\"intent\": [\"PURCHASE\", \"SPECIFICATION\", \"LOCATION\"],
\"relevance\": [\"OFFERING\"],
\"missingFromContent\": true,
\"limit\": $LIMIT
}" | jq -r '.data[].id' | jq -Rsc 'split("\n")[:-1]')
# 2. Fire the bulk job
curl -sf -X POST $BASE/produce/landing-page/bulk \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d "{ \"keywordIds\": $KW_IDS, \"pillarSlug\": \"$PILLAR\", \"concurrency\": 4 }"
echo "Bulk landing-page generation queued."Gotchas
- Credit budget. 100 pages × 40 credits = 4,000 credits. Check your balance first; the bulk endpoint reserves cost up front.
- Pillar template matters more than you think. A weak template produces 100 weak pages. Test with
produce.landing_pageon one or two keywords first; tune the template; then bulk. - Concurrency vs rate limits. Default 4 parallel is safe. Higher can throttle DataForSEO + Ahrefs.
- De-duplication. If two keywords are near-synonyms, you'll get two similar pages. Run
research.keyword.searchwith dedup first. - Don't auto-publish blindly. For the first batch, approve manually. Once you trust the template + refiner stack, auto-publish.
Related
- API: Production · landing page
- API: Production · publish
- API: Research · keyword
- Concept: Content Refiners
- Playbook: Keyword research for a brand in 20 minutes
Daily Reddit monitoring
Every morning, scrape your ICP's subreddits, score pain points by signal quality, and surface the top content opportunities — plus auto-generated blog briefs ready for approval.
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.