Landing Page API — Pillar-Driven SEO Landing Page Generation
REST API for generating SEO landing pages via a four-step pipeline (search intent, SEO metadata, page copy, final metadata). Each step is independently regenerable and governed by workspace-level pillars.
Generate, edit, and manage SEO landing pages. Each landing page runs a four-step pipeline (search intent → SEO metadata → page copy → final metadata), with every step independently regenerable, and templates governed by landing-page pillars at the workspace level.
Conceptual overview
A landing page in CitationBench is two records:
LandingPage— the page itself: keyword, title, slug, search intent, SEO metadata, body, status, indexing flagsLandingPagePillar— the template + schema governing pages of this type. Pillars define the JSON schema forlandingPageVariablesand the prompt template that fills them.
You typically have 4–8 pillars per workspace (e.g., pricing, comparison, feature, industry, use-case, location). Each pillar's pages share structure but differ in content.
The 4-step pipeline:
1. Search intent analysis → what does the searcher actually want?
2. SEO metadata → title, description, slug, schema, primary keyword
3. Page copy → body, filled to the pillar's template variables
4. Final metadata → second pass on (2) using the actual bodyEvery step is independently regenerable. Copy is right but meta is off? Re-run step 4. Search intent shifted? Re-run 1–4. Want a fresh draft? Re-run all.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/produce/landing-page | Generate one landing page |
| POST | /v1/produce/landing-page/bulk | Bulk generate from keyword list |
| GET | /v1/produce/landing-page | List pages |
| GET | /v1/produce/landing-page/{id} | Get one |
| PATCH | /v1/produce/landing-page/{id} | Update (manual edits, status) |
| DELETE | /v1/produce/landing-page/{id} | Delete |
| POST | /v1/produce/landing-page/{id}/regenerate | Re-run pipeline from a specific step |
| CRUD | /v1/produce/landing-page/pillar/{...} | Manage pillars |
POST /v1/produce/landing-page
Request
{
"keywordId": "kw_***",
"pillarSlug": "pricing",
"model": "claude-sonnet-4-6",
"options": {
"crawlExistingUrl": "https://acme.com/old-pricing",
"serpAnalysis": true,
"icpSegmentId": "seg_***"
}
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
keywordId | string | yes (one of) | — | Existing keyword |
keyword | string | yes (one of) | — | Bare keyword (auto-creates) |
briefId | string | yes (one of) | — | A brief produced by bootstrap_brand or content_gap |
pillarSlug | string | yes | — | Which pillar's template to use |
model | enum | no | workspace default | claude-sonnet-4-6, gpt-4o, gemini-2.5-pro |
options.crawlExistingUrl | string | no | — | Use an existing URL as input (optimization mode) |
options.serpAnalysis | boolean | no | true | Pull SERP before writing |
options.icpSegmentId | string | no | workspace primary | Tailor copy to a specific ICP |
refinerIds | string[] | no | pillar default | Refiners auto-applied after draft |
Response
{
"landingPageId": "lp_***",
"invocationId": "inv_***",
"agentId": "agt_***",
"skill": "produce.landing_page",
"status": "RUNNING",
"estimatedCost": { "credits": 38, "durationSeconds": 240 },
"pipeline": [
{ "step": "search_intent", "status": "QUEUED" },
{ "step": "seo_metadata", "status": "QUEUED" },
{ "step": "page_copy", "status": "QUEUED" },
{ "step": "final_metadata", "status": "QUEUED" }
]
}Final result
GET /v1/produce/landing-page/{landingPageId}:
{
"id": "lp_***",
"pillarSlug": "pricing",
"keyword": "project management software for engineering teams",
"title": "Project management software for engineering teams | Acme",
"slug": "project-management-software-for-engineering-teams",
"description": "Real-time capacity tracking and sprint planning ...",
"url": null,
"isPublished": false,
"isGoogleIndexRan":false,
"h1": "Real-time PM for engineering teams",
"category": "pricing",
"searchIntent": {
"summary": "Searcher comparing PM tools by feature; engineering-team specifics matter most",
"userQueries": ["capacity tracking", "sprint planning", "integrations with GitHub"]
},
"seoMetadata": {
"metaTitle": "Project management software for engineering teams | Acme",
"metaDescription": "...",
"schemaJsonLd": { "@type": "WebPage", "...": "..." }
},
"landingPageVariables": {
"hero": { "headline": "...", "subheadline": "...", "ctaText": "Start free" },
"featureBlocks": [{ "title": "...", "body": "..." }],
"pricingTable": [{ "tier": "Starter", "price": "$99/mo", "features": [...] }],
"faqs": [{ "q": "...", "a": "..." }]
},
"lastInvocation": {
"invocationId": "inv_***",
"agentId": "agt_***",
"skill": "produce.landing_page",
"status": "SUCCEEDED",
"raw": "Outlined the page around 3 feature blocks for engineering teams ...",
"files": ["agent-workspace/search-intent.md", "agent-workspace/draft-v1.html", "agent-output/page-vars.json"]
}
}POST /v1/produce/landing-page/bulk
curl -X POST .../v1/produce/landing-page/bulk -d '{
"keywordIds": ["kw_A", "kw_B", "kw_C", "kw_D"],
"pillarSlug": "pricing",
"concurrency": 3
}'Spawns one invocation per keyword.
POST /v1/produce/landing-page/{id}/regenerate
curl -X POST .../v1/produce/landing-page/lp_***/regenerate -d '{
"fromStep": "page_copy",
"preserveRefinements":true
}'fromStep can be search_intent, seo_metadata, page_copy, final_metadata. Steps before that are preserved.
CRUD: /v1/produce/landing-page/pillar/{...}
# List pillars
curl .../v1/produce/landing-page/pillar
# Create
curl -X POST .../v1/produce/landing-page/pillar -d '{
"slug": "comparison",
"name": "Comparison pages",
"description": "Side-by-side comparisons with competitors",
"promptTemplate":"Generate a comparison ...",
"schema": { "type": "object", "properties": { ... } },
"categories": ["competitor"],
"baseUrl": "/compare/",
"defaultRefinerIds": ["rfn_brand-voice"]
}'
# Update
curl -X PATCH .../v1/produce/landing-page/pillar/pil_***
curl -X DELETE .../v1/produce/landing-page/pillar/pil_***MCP
> Generate a landing page for "project management for engineering teams" in the pricing pillar.Claude calls produce.landing_page.create.
> Regenerate just the SEO metadata for lp_***.Claude calls produce.landing_page.regenerate with fromStep: "seo_metadata".
> Generate 50 comparison pages from my competitor list.Claude calls produce.landing_page.bulk_create.
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | One-of constraint violated |
| 404 | keyword_not_found | — |
| 404 | pillar_not_found | pillarSlug not in this workspace |
| 422 | step_dependency_missing | regenerate.fromStep requires the earlier steps to exist |
| 503 | model_unavailable | — |
Cost
| Mode | Credits |
|---|---|
| Full pipeline (4 steps) | ~38 |
| Regenerate from step 3 | ~28 |
| Regenerate just step 4 | ~7 |
| Bulk per page | same as single |
Use cases (string things together)
A. 100 programmatic landing pages overnight
KW_IDS=$(curl -sf -X POST .../v1/keywords/search -d '{
"intent": ["PURCHASE", "SPECIFICATION"],
"relevance":["OFFERING"],
"minVolume":50
}' | jq -r '.data[].id' | jq -Rsc 'split("\n")[:-1]')
curl -X POST .../v1/produce/landing-page/bulk -d "{
\"keywordIds\": $KW_IDS,
\"pillarSlug\": \"pricing\",
\"concurrency\":4
}"B. Refresh-existing mode
curl -X POST .../v1/produce/landing-page -d '{
"keyword": "project management for engineering teams",
"pillarSlug": "pricing",
"options": { "crawlExistingUrl": "https://acme.com/old-page" }
}'The agent reads the existing page, preserves what works, refreshes what doesn't.
C. Per-ICP variants
Generate 3 versions of the same page, one per ICP segment:
for SEG in seg_eng-leaders seg_founders seg_pm-managers; do
curl -X POST .../v1/produce/landing-page -d "{
\"keywordId\": \"kw_***\",
\"pillarSlug\": \"pricing\",
\"options\": { \"icpSegmentId\": \"$SEG\" }
}"
doneD. Auto-publish + index
LP=$(curl -sf -X POST .../v1/produce/landing-page -d '{...}' | jq -r '.landingPageId')
# wait for completion via SSE...
curl -X POST .../v1/produce/publish -d "{
\"landingPageId\": \"$LP\",
\"platformConfigId\": \"pfm_wordpress-main\"
}"
# Auto-fires indexing.gsc.submit if autoQueueOnPublish: trueRelated
- Concept: Content Refiners
- API: Production · blog post
- API: Production · refine
- API: Production · publish
- API: Indexing · gsc index (auto-fires after publish)
- Playbook: Generate 100 landing pages overnight
Blog post
REST API for generating, editing, and managing AI-written blog posts. Supports quick, research-backed, and deep-technical generation modes with multi-step refinement chains and bulk workflows.
Refine
REST API for applying reusable refiners to blog posts and landing pages. Chain brand voice, SEO hygiene, image insertion, and CTA refiners to keep hundreds of articles consistent without rewriting by hand.