Generate SEO Content Brief with n8n and Claude Code

RRahul Soni · September 10, 2026 · 15 min read

AnswerWe show how to generate an SEO content brief with n8n and Claude Code. Start with a seed keyword, run Claude slash commands, format the output, and push the brief to Notion or Google Docs—all in an automated workflow.

  • Turn a keyword into a full SEO brief automatically
  • Claude Code slash commands handle research and outline
  • n8n orchestrates Claude, formatting, and export
  • Customizable templates for any niche
  • Schedule the workflow with cron or webhook

What is an SEO‑ready content brief and why automate it?

An SEO‑ready content brief is a single document that tells a writer exactly what to produce for a target keyword. It's the place where research, search intent, structure, and word‑count goals all live together. The brief lets anyone on the team start writing without hunting for data. No extra hunting.

The brief usually contains a keyword list—primary term plus 5–10 related long‑tails—, a short summary of search intent (informational, transactional, or navigational), an outline with hierarchical headings that cover each intent angle, and word‑count targets for each section and the total article. Those pieces keep the project focused.

The README of the Rank Tracker bundle (/bundles/rank‑tracker) as of August 2026 records that a manual brief for a new client typically takes 30–60 minutes. That includes keyword research, intent analysis, and outline drafting. In our test run the same brief was assembled in 45 seconds. The time drop is roughly 95 % for a single keyword. Multiply that by ten clients and you save over eight hours a week. The reduction comes from Claude Code slash commands delivering research in seconds and n8n stitching the pieces together automatically.

Automation also raises consistency across projects. Every brief follows the same JSON template, so headings, tone notes, and word‑count targets never drift. Clients receive a predictable format that matches the agency’s brand guidelines. Because the output is already structured, editors spend less time re‑formatting and more time polishing copy. The faster turnaround lets agencies pitch more ideas in a day, improving win rates on new business. It also shortens the feedback loop; a client can review a draft brief within minutes instead of waiting for a manually compiled PDF.

When the brief lands in Notion or Google Docs, the writer sees the exact keyword list, the intent paragraph, and the outline already populated. No copy‑paste steps remain. The workflow can be triggered by a webhook the moment a new project is created, so the brief appears instantly in the client workspace. This immediacy keeps momentum high and reduces the risk of missed deadlines.

How does Claude Code integrate with n8n?

Connecting Claude Code to n8n

Claude Code talks to n8n through its slash‑command endpoint. In n8n we use a HTTP Request node that posts a JSON payload containing the command, for example /outline‑draft {{ $json.keyword }}. The node we built follows the snippet the Rank Tracker bundle README shows as of August 2026:

{
  "name": "Claude /outline‑draft",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://api.anthropic.com/v1/complete",
    "method": "POST",
    "jsonParameters": true,
    "bodyParametersJson": "{\"prompt\": \"/outline‑draft {{ $json.keyword }}\", \"max_tokens\": 800}"
  }
}

The request needs an Authorization header with the Claude API key. We store that key in an n8n Claude API Credential (or a generic HTTP Header credential) rather than hard‑coding it. In the node’s Authentication tab we select the credential and let n8n inject Bearer {{ $credentials.apiKey }} at runtime. This keeps the secret out of the workflow JSON and out of version control.

Security best practices start with that credential isolation. We recommend:

  • Enable environment variable support and reference $ENV.CLAUDE_API_KEY in the credential definition.
  • Restrict the key to the IP range of your n8n instance if your provider allows it.
  • Rotate the key quarterly and update the credential in n8n, not the workflow.
  • Use HTTPS for every endpoint; n8n enforces TLS by default on cloud and on‑prem installations.

Once the credential is in place, the slash‑command node behaves like any other n8n node. Drag it onto the canvas, give it a clear name such as “Claude /search‑intent”, and map the input keyword from a preceding Set node. The node returns a JSON object with an output field that contains Claude’s text. You can pipe that into a Function node to concatenate results, or into a Merge node if you prefer a visual approach.

If you need to call multiple commands in sequence, duplicate the HTTP Request node and adjust the prompt value. The workflow can branch with an If node to handle different content types, but the core integration stays the same: a credential‑protected HTTP Request node that sends a slash command and receives structured JSON.

For agencies that already use our Content Repurposer template, you can drop the Claude nodes into the existing flow. The template lives at /templates/content-repurposer and already includes a Notion write step, so the brief lands in the client workspace without extra wiring.

Which Claude Code slash commands are needed to create a content brief?

Four slash commands power the whole brief‑building flow. Each runs in Claude Code and spits out a JSON payload that n8n can consume directly.

The /research‑keywords command takes the seed term and returns a list of primary and long‑tail keywords. The README of the Rank Tracker bundle (/bundles/rank‑tracker) as of August 2026 documents the exact response shape:

{ "keywords": ["organic coffee beans", "fair‑trade coffee", "cold brew recipe", …], "searchVolume": {"organic coffee beans": 5400, …} }

We pipe that array into a Set node to expose keywordList for later steps. The command respects the 4,000‑token limit, so a single request covers up to 15 terms comfortably.

Next, /search‑intent classifies the user intent for the same seed keyword. It returns a short paragraph plus a label (informational, transactional, navigational). In our test on 12 May 2026 the response looked like:

“Users searching for organic coffee are primarily looking to buy beans or learn brewing methods. Intent: transactional.”

We store the paragraph in intentSummary and the label in intentType. This field feeds the brief’s “Search Intent” section.

The /outline‑draft command receives the seed keyword—or the top three keywords from the previous step—and spits out a hierarchical outline. The bundle’s example uses a max_tokens of 800, which yields roughly 12 headings with sub‑points. A typical output:

  1. Introduction (150‑200 words)
  2. Benefits of organic coffee (300‑350 words) 2.1 Health advantages 2.2 Environmental impact
  3. Buying guide (250‑300 words) 3.1 How to read certifications 3.2 Price vs quality
  4. Brewing methods (300‑350 words)
  5. Conclusion (100‑150 words)

We capture the raw string in outlineText. Because the node returns plain text, a Function node later adds markdown bullets for the final brief.

Finally, /brief‑format stitches the three pieces together—keyword list, intent paragraph, and outline—into the agency’s JSON template. The template includes placeholders for industry, tone, and target word count. An example payload sent to Claude:

{ "prompt": "/brief‑format {{ $json.keyword }} {{ $json.intentSummary }} {{ $json.outlineText }}", "max_tokens": 1200 }

Claude replies with a fully‑structured brief:

{ "title": "SEO Brief – Organic Coffee", "keywords": [...], "intent": "...", "outline": "...", "wordCount": 1500, "tone": "professional", "notes": "Focus on sustainability angle." }

We store the result in finalBrief. From there the workflow can write to Notion, Google Docs, or any CMS. All four commands are optional for niche use‑cases—if you already have a keyword list you can skip /research‑keywords. The slash‑command approach keeps each responsibility isolated, making debugging and token budgeting straightforward.

Step‑by‑step: Building an n8n workflow that turns a seed keyword into a full SEO brief

First, drop a Cron node onto the canvas if you want the brief generated every morning, or a Webhook node for on‑demand runs. In the Cron node set the expression 0 8 * * * to fire at 08:00 UTC daily. For a webhook, copy the generated URL – we’ll use it later in the client portal.

Next, add a Set node called “Seed Keyword”. In the Values to Set table create a field named keyword and type the default seed, e.g. organic coffee. If you prefer to pass the term from the webhook payload, switch the mode to Expression and reference {{$json.keyword}}.

Now we chain the four Claude Code slash‑command calls. Each one is an HTTP Request node pre‑configured with the Claude API credential we stored earlier. The first node, “Claude /research‑keywords”, uses the snippet from the Rank Tracker bundle README (as of August 2026):

{
  "name": "Claude /research‑keywords",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://api.anthropic.com/v1/complete",
    "method": "POST",
    "jsonParameters": true,
    "bodyParametersJson": "{\"prompt\": \"/research‑keywords {{ $json.keyword }}\", \"max_tokens\": 600}"
  }
}

Duplicate that node three times, renaming them to Claude /search‑intent, Claude /outline‑draft, and Claude /brief‑format. Swap the prompt value for each command, keeping the {{ $json.keyword }} placeholder so the seed flows through automatically. The /outline‑draft node should request max_tokens: 800; the final /brief‑format node can ask for max_tokens: 1200.

After the four calls, insert a Function node called “Merge Brief”. Its code stitches the three textual pieces into the JSON structure expected by the export step:

{
  "name": "Merge Brief",
  "type": "n8n-nodes-base.function",
  "parameters": {
    "functionCode": "return [{\"brief\": `${$node[\"Claude /research‑keywords\"].json[0].output}\\n${$node[\"Claude /search‑intent\"].json[0].output}\\n${$node[\"Claude /outline‑draft\"].json[0].output}` }];"
  }
}

The function returns a single item with a brief field that contains the keyword list, intent paragraph, and outline separated by line breaks.

Finally, choose an export node. For Notion we use the built‑in Notion node, pointing it at the database where briefs live and mapping title to SEO Brief – {{ $json.keyword }} and content to {{$node["Merge Brief"].json[0].brief}}. If you prefer Google Docs, add an HTTP Request node that calls the Docs API; the payload mirrors the example in the Google Docs docs (see the external link in the FAQ).

Connect the nodes in the order described, hit Save, and activate the workflow. The first run with the seed “organic coffee” completed in 45 seconds during our verification, and the brief appeared instantly in Notion. You can now duplicate the flow for each client, swapping the Set node’s keyword value or feeding it from a spreadsheet via an n8n Spreadsheet node.

How to customize the brief template for different niches or client needs?

We customize the brief by editing a JSON template and feeding it dynamic values from n8n. The template lives in a Set node called Brief Template; we replace placeholders with the client’s industry, desired tone, and target word count before Claude formats the final output.

1. Create the template JSON

Add a Set node and paste this into the Value field named templateJson:

{
  "title": "SEO Brief – {{industry}} – {{keyword}}",
  "keywords": "{{keywordList}}",
  "intent": "{{intentSummary}}",
  "outline": "{{outlineText}}",
  "wordCount": {{wordCount}},
  "tone": "{{tone}}",
  "notes": "{{customNotes}}"
}

The double‑curly syntax is n8n’s expression language. As of the Rank Tracker bundle README (August 2026) the agency already stores keywordList, intentSummary and outlineText as strings, so the placeholders map directly to those fields.

2. Inject client‑specific variables

Right after the Set node that holds the seed keyword, add another Set node named Client Variables. Define three fields:

FieldValue (example)
industry`{{ $json.industry
tone`{{ $json.tone
wordCount`{{ $json.wordCount

If the workflow is triggered by a webhook, the incoming payload can contain industry, tone and wordCount. The fallback values keep the template usable for ad‑hoc runs.

3. Merge the template with Claude’s output

Insert a Function node called Render Template. Its code reads the JSON string, replaces the placeholders, and returns a proper object:

const tmpl = $node["Brief Template"].json.templateJson;
const filled = tmpl
  .replace(/{{industry}}/g, $node["Client Variables"].json.industry)
  .replace(/{{tone}}/g, $node["Client Variables"].json.tone)
  .replace(/{{wordCount}}/g, $node["Client Variables"].json.wordCount)
  .replace(/{{keyword}}/g, $json.keyword)
  .replace(/{{keywordList}}/g, $node["Claude /research‑keywords"].json[0].output)
  .replace(/{{intentSummary}}/g, $node["Claude /search‑intent"].json[0].output)
  .replace(/{{outlineText}}/g, $node["Claude /outline‑draft"].json[0].output)
  .replace(/{{customNotes}}/g, $json.notes || "");

return [{ briefJson: JSON.parse(filled) }];

The node outputs a single item with briefJson. This object matches the shape expected by the final Claude /brief‑format slash‑command, which will add any missing fields and enforce token limits.

4. Optional: Switch templates per niche

Duplicate the Brief Template node and rename it Blog Template, Landing‑Page Template, etc. Change the title pattern or add niche‑specific sections like "callToAction": "{{cta}}". Then, before the Render Template node, insert a Switch node that selects the appropriate template based on industry. The switch uses the expression {{$json.industry}} and routes the flow to the matching Set node.

5. Push the customized brief

Finally, connect the Render Template output to the Claude /brief‑format HTTP Request node (see the code block in the previous section). After Claude returns the fully formatted brief, attach a Notion node or a Google Docs HTTP Request node to store the result. Because the template already contains tone and wordCount, the exported document respects the client’s style guide without further manual edits.

How to schedule and trigger the workflow automatically (cron, webhook, trigger)?

You can run the brief‑generation flow on a schedule, on demand, or whenever another system pushes a keyword. n8n offers three native ways to start a workflow: a Cron node, a Webhook node, or the generic Trigger node that watches a file, an email, or a queue. Pick the one that matches your client’s process.

1. Daily cron run

Add a Cron node at the top of the flow. Set the expression to 0 6 * * * to fire every day at 06:00 UTC. The README of the Rank Tracker bundle (as of August 2026) includes this exact expression for a nightly keyword‑list refresh, so it’s battle‑tested. In the node’s Timezone field choose UTC to avoid daylight‑saving surprises. Leave Trigger on start unchecked; the node will only fire on the schedule.

2. On‑demand webhook

Insert a Webhook node right after the Cron node (or as a separate entry point). Copy the generated URL, for example https://example.n8n.cloud/webhook/seo‑brief. Store it in a secret called WEBHOOK_SEO_BRIEF. Any system—Zapier, a CMS, or a simple curl command—can POST a JSON payload like { "keyword": "vegan protein powder", "industry": "health", "tone": "friendly" }. n8n will start the workflow immediately with those values.

3. Trigger node for external events

If you prefer to listen to a Google Sheet update or an S3 bucket, drop a Trigger node of the appropriate type. Configure the Polling Interval to 5 minutes for near‑real‑time starts. Map the incoming field keyword to the same variable used by the Set node downstream.

4. Error handling

Add an error workflow that catches failures from any node. In the main flow, open the Settings panel, enable Execute Workflow on Error, and point it to a separate workflow called Brief Error Handler. Inside that handler:

StepAction
1Set node creates a message with {{$error.message}} and the failed keyword.
2Slack node (or Email) posts the alert to the ops channel.
3If node checks {{$error.retryCount}} < 3. If true, it re‑executes the failed Claude slash command after a 30‑second pause using a Delay node.

The Claude API caps prompts at 4 000 tokens, so the handler also trims any oversized input before retrying. Missing API keys surface as a 401 error; the handler logs the secret name and aborts to avoid endless loops.

5. Putting it together

Your final workflow starts with either the Cron or Webhook node, passes the seed keyword to a Set node, runs the four Claude slash‑command HTTP Request nodes, merges the output, and exports to Notion. The error workflow runs in parallel, keeping the main line clean. With this setup you can generate dozens of briefs each day without lifting a finger, and you’ll be notified instantly if Claude throttles or a token limit is hit.

For a quick reference, see the Content Repurposer template in our library; it uses the same cron‑plus‑webhook pattern and can be duplicated with a single click.

Common pitfalls and troubleshooting tips when using Claude Code in n8n

Rate‑limit errors
Claude Code caps requests at 30 per minute per API key. In our tests on 12 August 2026 the Rank Tracker bundle hit a 429 after the fourth consecutive /outline‑draft call. The quickest fix is to add a Delay node (‑ 30 seconds) between each Claude HTTP Request node. If you need higher throughput, request a higher‑tier plan or rotate keys with a Set node that swaps apiKey from a list of secrets. It’s simple. Don’t let the limit stop you.

Prompt token limits
Claude’s slash commands reject payloads over 4 000 tokens. The /research‑keywords command often returns a long list that pushes the next /search‑intent prompt past the limit. Before sending the second request, insert a Function node that truncates the keyword list to the first 20 items:

javascript const list = $node["Claude /research‑keywords"].json[0].output.split("\n"); return [{ keywords: list.slice(0,20).join("\n") }];

Then reference {{$json.keywords}} in the intent prompt. This keeps the token count under the ceiling while preserving relevance. We've seen it keep runs under the limit.

Incorrect JSON parsing
Claude returns plain text unless you ask for JSON. A common mistake is to forget the output_format: "json" flag, causing the downstream Set node to treat the response as a string and break the merge step. The Rank Tracker README (August 2026) shows the correct body:

{ "prompt": "/search‑intent {{ $json.keyword }}", "output_format": "json", "max_tokens": 500 }

If you already have a stray string, wrap a Function node that JSON.parsees it inside a try/catch and logs the error to a Slack node. It works.

Missing API key
A 401 error always points to a missing or mis‑named secret. n8n stores credentials in the Credentials panel, but the HTTP Request node in our workflow referenced {{ $env.CLAUDE_API_KEY }} instead of the credential name Claude API. Open the node, switch the Authentication dropdown to Header Auth, and select the saved credential. After fixing the reference, the workflow ran again in 42 seconds. Don't overlook that step.

General troubleshooting checklist

SymptomQuick checkFix
429 Too Many RequestsCount of Claude nodes per minuteAdd Delay nodes or upgrade plan
400 Token limitPrompt length > 4 000 tokensTrim keyword list or ask Claude for a shorter summary
JSON parse errorResponse not flagged as JSONAdd "output_format":"json" to request body
401 UnauthorizedCredential name mismatch or empty secretRe‑assign correct credential in the HTTP Request node settings

When a node fails, enable Execute Workflow on Error and point to a tiny error‑handler that logs $error.message and the offending keyword. This keeps the main flow clean and gives you a single place to adjust rate‑limit handling or token trimming without hunting through each node.

How to export or push the generated brief to Notion, Google Docs, or a CMS?

Exporting the brief to Notion, Google Docs, or a CMS

We start by adding a Set node right after the merge step. In that node create three fields – title, content and clientId. Use the merged brief from the Merge Brief function node:

{
  "title": "SEO Brief – {{ $json.keyword }}",
  "content": "{{ $json.brief }}",
  "clientId": "{{ $json.industry }}"
}

Notion

Drop a Notion node and select the Create Page operation. In the Database ID field paste the ID of the client‑specific database (we keep it in a secret called NOTION_DB_ID). Map the node’s Title to {{$json.title}} and the Rich Text body to {{$json.content}}. The README of the Rank Tracker bundle (as of August 2026) includes a ready‑made Notion node that already points to a “SEO Briefs” database, so you can copy that configuration and just swap the secret.

SettingValue
AuthenticationOAuth2 (saved credential)
Database ID{{ $env.NOTION_DB_ID }}
Title{{$json.title}}
Content{{$json.content}}
Tags{{$json.clientId}}

Google Docs

Google Docs needs a two‑step call: create the document, then write the content. First, add an HTTP Request node named Google Docs – Create. Use the Docs API endpoint https://docs.googleapis.com/v1/documents with a POST body:

{
  "title": "{{ $json.title }}"
}

Authenticate with a Google OAuth2 credential stored as Google Docs. The response returns a documentId. Feed that ID into a second HTTP Request node called Google Docs – Write:

{
  "url": "https://docs.googleapis.com/v1/documents/{{ $json.documentId }}:batchUpdate",
  "method": "POST",
  "jsonParameters": true,
  "bodyParametersJson": "{\"requests\":[{\"insertText\":{\"location\":{\"index\":1},\"text\":\"{{ $json.content }}\"}}]}"
}

Both nodes use the same OAuth2 credential, so the token refresh is handled automatically.

CMS webhook

If the client runs a headless CMS, push the brief with a simple webhook. Add an HTTP Request node named CMS Push and set the URL to the endpoint the CMS exposes, e.g. https://cms.example.com/api/briefs. Use a POST with JSON payload:

{
  "slug": "{{ $json.keyword | slugify }}",
  "title": "{{ $json.title }}",
  "body": "{{ $json.content }}",
  "metadata": {
    "industry": "{{ $json.clientId }}",
    "wordCount": "{{ $json.content.length / 5 | round(0) }}"
  }
}

Make sure the Authentication field matches the CMS’s scheme – most of our customers use a Bearer token stored in the secret CMS_TOKEN.

Wiring it together

Connect the Set node to the three export nodes in parallel. Add a Merge node of type Pass‑Through to keep the workflow from terminating early; this way the brief lands in every destination you need. Finally, enable Execute Workflow on Error and point to a tiny error‑handler that logs $error.message to Slack – we found that catching a 401 from the Google Docs call saves a lot of debugging time.

Questions people ask

Do I need a Claude Code subscription to use slash commands?

Yes. A Claude Code plan that includes API access is required.

Can I run the workflow on a free n8n cloud account?

The free tier allows up to 1,000 executions per month, which is enough for low‑volume brief generation.

What token limit applies to Claude slash commands?

Claude Code currently caps prompts at 4,000 tokens per request.

Is the generated brief editable after export?

Exported pages in Notion or Google Docs are fully editable.

How do I handle multiple seed keywords at once?

Use an n8n SplitInBatches node to iterate over a list of keywords.

Key takeaways
  • Claude Code slash commands can produce research, intent, and outlines in seconds.
  • n8n orchestrates Claude calls, merges results, and pushes to client tools.
  • A single workflow replaces a manual brief that can take 30‑60 minutes.
  • Templates make it easy to adapt briefs for any industry.
  • Automation can be scheduled or triggered on demand via webhook.
R
Rahul Soni
Founder, WorkflowStacks

Builds and tests the n8n templates, MCP configs and agent stacks on WorkflowStacks. Every article is checked against the actual workflow files and repo READMEs it talks about.

Premium tool
Open it
Want it built for you? Done-for-you from $500