Content briefs are where most content operations quietly stall. A good brief — target keyword, search intent, the questions real searchers ask, the structure that already ranks, and the entities you must cover — takes an experienced strategist 30 to 45 minutes per keyword when done by hand. Multiply that across a 200-keyword backlog and the brief becomes the bottleneck, not the writing.
This guide walks through a production-grade n8n workflow that turns a single keyword into a structured, SEO-ready content brief automatically. It pulls live SERP data, extracts the heading structure and questions from the pages that already rank, and uses Claude to synthesize an outline with intent analysis and entity coverage. You get working node configurations, JSON snippets, and throughput numbers from a real 50-keyword test run.
This is written for automation and ops engineers who already know what an API, a webhook, and JSON are, but not necessarily the internals of n8n.
What the pipeline does
The workflow is a linear chain of eight nodes. In plain terms, data flows like this: a trigger fires on a schedule, reads a list of pending keywords from a Google Sheet, fetches the live search results for each one, scrapes the ranking pages, aggregates their structure, asks Claude to build a brief, writes the brief back to the sheet, and posts a summary to Slack.
Visualized as a node graph: Schedule Trigger → Google Sheets (read) → Loop Over Items → HTTP Request (SERP) → HTTP Request (page fetch) → Code (extract structure) → Anthropic Chat Model → Google Sheets (write) → Slack. Every step is a standard n8n node, so there is nothing to deploy beyond n8n itself plus two API keys.
Prerequisites
You need a running n8n instance (cloud or self-hosted), a SERP data source (Bright Data SERP API, SerpAPI, or any provider that returns JSON for a query), an Anthropic API key for Claude, and a Google Sheet with two columns: keyword and status. If you self-host, the same patterns apply — see our guide on running n8n in queue mode with Docker, Redis and Postgres for handling many keywords concurrently.
Step 1 — Trigger and keyword input
Use a Schedule Trigger set to run every morning, or a Webhook if you want to brief on demand from another tool. Connect it to a Google Sheets node in Read Rows mode, filtered to rows where status is empty. To keep API spend predictable, cap the batch with a Limit node (for example, 25 keywords per run).
Each incoming item now looks like this:
{
"keyword": "n8n error handling",
"status": ""
}
Step 2 — Pull live SERP data with the HTTP Request node
Add an HTTP Request node pointing at your SERP provider. The exact parameters vary, but the shape is always the same — pass the keyword as a query parameter and request JSON back:
Method: GET
URL: https://api.your-serp-provider.com/search
Query Parameters:
q: ={{ $json.keyword }}
num: 10
gl: us
hl: en
Response Format: JSON
The response contains the top organic results. Map them down to just what the brief needs — title, link, and snippet — with a small Set or Code node so you are not carrying a huge payload through the rest of the run.
Handling rate limits and pagination
SERP and scraping APIs throttle aggressively. Wrap the request with retry-and-backoff and respect any pagination cursors rather than firing requests in a tight loop. We covered the exact node settings for this in our deep dive on HTTP Request pagination, batching and rate-limit backoff — reuse that pattern here so a burst of keywords does not get you blocked.
Step 3 — Extract competitor structure
Loop over the top results with a second HTTP Request node to fetch each ranking page, then a Code node to extract its heading outline. The goal is to learn what subtopics Google already rewards. A minimal extractor:
// Code node (Run Once for Each Item)
const html = $json.data || "";
const headings = [...html.matchAll(/<h[23][^>]*>(.*?)</h[23]>/gi)]
.map(m => m[1].replace(/<[^>]+>/g, "").trim())
.filter(Boolean)
.slice(0, 15);
return { json: { url: $json.link, headings } };
Aggregate every page's headings into one array with an Aggregate node. You now have a de-duplicated map of the subtopics that the current top 10 collectively cover — the raw material for a brief that is comprehensive rather than generic.
Step 4 — Generate the brief with Claude
Add an Anthropic Chat Model node (or a generic HTTP Request to the Messages API). Feed it the keyword plus the aggregated competitor headings, and ask for structured JSON so the output is machine-usable downstream. A prompt that works well:
You are an SEO content strategist. Given a target keyword and the
headings used by the current top-ranking pages, produce a content brief.
Keyword: {{ $json.keyword }}
Competitor headings: {{ $json.headings }}
Return JSON only, with these fields:
- search_intent: one of informational, commercial, transactional
- title_options: array of 3 SEO titles
- outline: array of H2/H3 sections with a one-line note each
- entities: array of must-cover terms and concepts
- questions: array of 4 People-Also-Ask style questions
- gap: one subtopic competitors miss that we should own
Because Claude returns strict JSON, the next node can consume it directly. If you want to expose this brief generator to other AI tools or agents, you can publish the whole workflow as a callable endpoint — see how we did exactly that in exposing n8n SEO workflows as Claude tools via the MCP Server Trigger.
Step 5 — Write back and notify
Parse Claude's JSON, then use a Google Sheets node in Update Row mode to write the brief next to its keyword and flip status to done. Finish with a Slack node that posts a one-line digest — "12 new briefs ready for review" — so a human can pick up the writing. Closing the loop later is easy: feed published URLs into a rank tracker like the one in our Google Search Console rank-tracking workflow and you have planning and measurement on one platform.
Results: manual vs automated
We ran the pipeline against a 50-keyword backlog and compared it to the team's manual process. The numbers from that test run:
| Metric | Manual | n8n pipeline |
|---|---|---|
| Time per brief | ~35 min | ~40 sec |
| 50 keywords total | ~29 hours | ~33 min |
| Cost per brief (Claude API) | — | ~$0.02–0.05 |
| Structural consistency | variable | identical schema |
The point is not that the machine writes the article — it does not. The point is that it removes the 29 hours of mechanical SERP analysis, so strategists spend their time on judgment and editing instead of copy-pasting headings into a doc.
Takeaways
A content brief is mostly structured data assembly, which is exactly what n8n is good at. The three ingredients that make this reliable are: backoff on the SERP calls so you do not get throttled, a strict-JSON contract with Claude so downstream nodes never break on free-form text, and a status column so reruns are idempotent. Start with one keyword, confirm the brief quality, then scale the batch.
Want more workflows like this every week? Bookmark n8nfuel and subscribe for new n8n recipes. If you are moving toward fully agentic automation next, read our walkthrough of building a RAG pipeline in n8n with Qdrant and Claude.
Frequently asked questions
Do I need a paid SERP API, or can I scrape Google directly?
Scraping Google directly will get your IP blocked quickly and is unreliable at scale. A dedicated SERP API (Bright Data, SerpAPI, and similar) returns clean JSON and handles the bot-detection problem for you, which is why the workflow uses one in the HTTP Request node.
Which Claude model should I use for the brief?
A mid-tier model like Claude Sonnet is the sweet spot — it follows the strict-JSON instruction reliably and keeps cost in the few-cents-per-brief range. Reserve the largest models for tasks that need deeper reasoning than outline generation.
Can the pipeline write the full article too?
It can, but you should not let it publish unattended. Treat the brief as the deliverable and keep a human in the loop for drafting and editing. Auto-published, unreviewed content is exactly what Google's scaled-content-abuse policy targets.
How do I run hundreds of keywords without timeouts?
Batch them with a Limit node, add backoff on the API calls, and run n8n in queue mode so executions are distributed across workers instead of blocking a single process.