Most n8n tutorials stop at "drop in the Anthropic node, type a prompt, done." That works for a demo and falls apart in production, because the thing you actually need from an LLM inside a workflow is not prose. It is a predictable data structure that the next node can read without guessing.
This guide covers both ways to call Claude from n8n, when each one is right, and how to get JSON back that your Set node, your database, and your Slack message can all rely on. Everything here was checked against n8n 1.109.1.
The Two Ways to Call Claude From n8n
There are exactly two paths worth using.
- The built-in Anthropic node. Ships with n8n. You pick a model, type a prompt, and get text back. Handles credentials for you.
- An HTTP Request node pointed at the Messages API. You build the request body yourself. More setup, full access to every parameter the API supports.
Most workflows should start with the built-in node and move to HTTP when they hit its ceiling. That ceiling arrives sooner than people expect.
When the Built-In Anthropic Node Is the Right Call
Use it when the output is for a human to read, or when a small amount of drift does not matter.
Good fits:
- Drafting a reply that a person reviews before sending
- Summarizing a long support ticket into a paragraph
- Rewriting text for tone
- Classifying into a handful of categories where you can tolerate cleaning up the odd stray word
If a human is the next step in the chain, the built-in node is less code and less to maintain. Do not reach for HTTP out of habit.
Why the Built-In Node Cannot Give You Reliable JSON
Here is the concrete reason to switch. In n8n 1.109.1, the Anthropic node's Message operation exposes these parameters and no others: model, messages, system, maxTokens, temperature, topP, topK, simplify, plus toggles for web search and code execution.
There is no tool_choice parameter. There is no structured output or response format parameter. The string tool_choice does not appear in the node's message operation source at all.
That matters because forcing a tool call is the mechanism that makes Claude return a guaranteed shape. Without access to that parameter, your only lever is asking nicely in the prompt, and "respond only with JSON" is a request, not a constraint. It works most of the time. Most of the time is not a spec.
So the rule is simple. If a downstream node parses the output as data, use HTTP Request. If a human reads the output, the built-in node is fine.
Setting Up the HTTP Request Node
Add an HTTP Request node and configure it like this.
- Method: POST
- URL:
https://api.anthropic.com/v1/messages - Authentication: Generic Credential Type, then Header Auth
- Send Headers: on, add
anthropic-versionwith value2023-06-01 - Send Body: on, Body Content Type JSON
The anthropic-version value is not something to guess at. 2023-06-01 is the exact value n8n's own Anthropic node sends on every request it makes, which you can confirm in its transport layer.
The Minimum Working Request Body
This is the smallest body the Messages API accepts. All three fields are required.
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{ "role": "user", "content": "Summarize this ticket in two sentences." }
]
}
messages is an array of turns, each with a role of user or assistant. A system prompt is not a message; it goes in a separate top-level system field.
Which Model ID to Use
Use claude-sonnet-5 as your default for workflow automation. It is the balance point between cost and capability for extraction, classification, and routing, which is most of what runs inside n8n.
One warning that will save you a debugging session. Model IDs with 2024 date suffixes in them, the claude-3-5-sonnet-20241022 generation, are dead. Copy-pasting one out of an old blog post gets you a 404 from the API, not a helpful message about deprecation. Current IDs carry no date suffix at all.
Storing the API Key as a Credential, Not in the Workflow
Never paste the key into the header value field. The workflow JSON is exported, committed, shared, and pasted into chat windows. A key in there is a key that leaks.
In n8n, create a Header Auth credential with name x-api-key and the value set to your key, then select that credential on the HTTP Request node. The key now lives in n8n's encrypted credential store and the exported workflow JSON references it by ID instead of containing it.
Note that Anthropic uses x-api-key, not the Authorization: Bearer pattern you may be used to from other APIs. Getting this wrong returns a 401 that looks like a bad key.
Getting Reliable JSON Instead of Prose
This is the single most useful technique in this guide, so it gets a full example.
Instead of asking Claude to write JSON, you define a tool whose input schema is your desired output shape, then force Claude to call it. The API validates the arguments against your schema. You read the structure out of the tool call rather than parsing free text.
Three parts make it work: a tools array, a tool_choice pinned to that tool by name, and reading the result from the right place in the response.
A Worked Forced Tool Use Example
Say you are triaging inbound support email and you need a category, an urgency score, and a one-line reason. Here is the full request body.
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"system": "You triage inbound support email. Always call the record_triage tool.",
"messages": [
{ "role": "user", "content": "Subject: charged twice\n\nI got billed two times this month and support has not replied in four days." }
],
"tools": [
{
"name": "record_triage",
"description": "Record the triage decision for one support email.",
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "bug", "feature_request", "account", "other"],
"description": "The single best category for this email."
},
"urgency": {
"type": "integer",
"description": "Urgency from 1 (can wait) to 5 (respond today)."
},
"reason": {
"type": "string",
"description": "One sentence explaining the category and urgency."
}
},
"required": ["category", "urgency", "reason"],
"additionalProperties": false
}
}
],
"tool_choice": { "type": "tool", "name": "record_triage" }
}
The tool_choice line is what the built-in node cannot express. It removes Claude's option to reply with text instead of calling your tool.
Reading the Tool Call Out of the Response
The response content is an array of blocks, not a single string. With forced tool use you want the block whose type is tool_use, and your data is its input field.
{
"id": "msg_01...",
"model": "claude-sonnet-5",
"stop_reason": "tool_use",
"content": [
{
"type": "tool_use",
"id": "toolu_01...",
"name": "record_triage",
"input": {
"category": "billing",
"urgency": 4,
"reason": "Duplicate charge with no support response for four days."
}
}
],
"usage": { "input_tokens": 512, "output_tokens": 78 }
}
In an n8n expression, the path to your data is this.
{{ $json.content.find(b => b.type === 'tool_use').input }}
Do not write $json.content[0].input and move on. The block order is not contractual, and a thinking or text block landing at index 0 turns a working workflow into an undefined at 3am. Find the block by type.
Enums and Required Fields Do the Real Work
A schema that says "category": {"type": "string"} gets you a string. That is barely a constraint. The two lines that actually buy you reliability are enum and required.
enum means you get one of five known values instead of "Billing Issue" one run and "billing_problem" the next, which then needs normalizing in a Code node forever. required means the field is present rather than quietly absent. Add "additionalProperties": false to stop extra keys appearing.
Spend your effort on the schema, not the prompt. The schema is enforced; the prompt is advice.
Handling Malformed JSON Anyway
Forced tool use makes malformed output rare. Rare is not never, and workflows run unattended, so build the parser defensively.
Add a Code node after the HTTP Request node. Run it in "Run Once for Each Item" mode.
const content = $input.item.json.content;
let parsed = null;
let error = null;
try {
if (!Array.isArray(content)) {
throw new Error('content is not an array');
}
const block = content.find((b) => b && b.type === 'tool_use');
if (!block) {
throw new Error('no tool_use block in response');
}
const input = block.input;
if (!input || typeof input !== 'object') {
throw new Error('tool_use block has no input object');
}
for (const field of ['category', 'urgency', 'reason']) {
if (input[field] === undefined) {
throw new Error('missing required field: ' + field);
}
}
parsed = input;
} catch (e) {
error = e.message;
}
return {
json: {
ok: error === null,
error,
data: parsed,
raw: error === null ? undefined : content,
},
};
Note what this node does not do. It does not throw. A thrown error in a Code node stops the execution, and a stopped execution is a silent failure you find out about from a customer.
Routing the Failure to a Human
Put an If node after the parser and branch on {{ $json.ok }}.
The true branch continues the workflow. The false branch goes to a Slack or email node carrying $json.error and $json.raw, so a person sees the specific failure and the actual response body. That is the whole pattern: the workflow keeps running for the 99 percent, and the 1 percent becomes a message to a human instead of a crash.
A retry is tempting here, and a single retry at temperature 0 is reasonable. What is not reasonable is an unbounded retry loop against a paid API, which turns one bad input into a bill.
The N8N_BLOCK_ENV_ACCESS_IN_NODE Trap
If you read configuration from $env in a Code node, this one will bite you on a future upgrade.
In n8n 1.109.1 the check is a strict opt-in. Env access is blocked only when N8N_BLOCK_ENV_ACCESS_IN_NODE is exactly the string true, so the current default of unset or empty means $env works fine.
The deprecation warning n8n prints at startup says the default is changing:
The default value of N8N_BLOCK_ENV_ACCESS_IN_NODE will be changed from false to true in a future version. If you need to access environment variables from the Code Node or from expressions, please set N8N_BLOCK_ENV_ACCESS_IN_NODE=false.
When that flips, every $env read in a Code node or expression throws an error reading access to env vars denied. Two things to do now. Set N8N_BLOCK_ENV_ACCESS_IN_NODE=false explicitly if you depend on $env, so the default change is a non-event. Better, move anything secret out of $env and into an n8n credential, which is where it belonged anyway.
Cost Control: Check usage, Do Not Estimate
Every Messages API response carries a usage object with input_tokens and output_tokens. Read it. Do not estimate from character counts.
Estimates are wrong in both directions for reasons you do not control. System prompts, tool schemas, and message history all count as input, and a tool schema is input tokens you pay for on every single call whether or not the tool gets used. Retries double a run's cost. Your four-word prompt is not the size of the request.
The habit that actually controls spend is logging usage from every run into a sheet or table alongside the workflow name. After a week you have real per-run numbers for the runs you actually make, and you will usually find one node accounting for most of the bill.
Keep max_tokens Tight on Extraction Workflows
max_tokens is a ceiling, not a target, so setting it high costs nothing by itself. But a high ceiling on a structured-extraction workflow hides a bug rather than preventing one.
If your triage tool call should produce roughly 80 output tokens and you set max_tokens to 8192, a run that goes wrong can generate a long rambling response and you pay for all of it. Set it to a few hundred for extraction work. If a response hits the ceiling, stop_reason comes back as max_tokens and you have a signal to investigate instead of an invisible charge.
Temperature for Workflow Calls
For extraction, classification, and routing, use temperature 0. You want the same input to produce the same output, because a workflow that behaves differently on identical input cannot be debugged.
Leave temperature higher only on nodes whose output a human reads and edits. If you want the full reasoning on this setting, we wrote it up separately in the Claude temperature settings guide.
Common Mistakes
Parsing content[0] Instead of Finding the Block
Covered above, and it is the single most common breakage. The response content is an array whose block order is not guaranteed. Always find by type.
Asking for JSON in the Prompt Instead of Forcing a Tool
"Reply with only valid JSON, no markdown" works until the day Claude wraps it in a fenced code block and your JSON.parse throws. Forced tool use removes the failure mode instead of reducing its frequency.
Putting the API Key in the Node Instead of a Credential
The workflow JSON gets exported and shared. Credentials do not travel with it. Use Header Auth.
Using a 2024 Model ID
claude-3-5-sonnet-20241022 and its generation are gone. Current IDs have no date suffix.
Letting a Code Node Throw on Bad Output
A thrown error stops the execution. Catch, set an ok flag, and branch with an If node so failures reach a person.
Retrying Without a Bound
One retry is fine. A loop that retries until success against a metered API is a way to turn a malformed response into a large invoice.
Ignoring the usage Object
If you are not logging input_tokens and output_tokens, you do not know what your workflow costs. You have a guess.
Sending Authorization: Bearer
Anthropic uses the x-api-key header. The Bearer pattern returns a 401 that reads like an invalid key and sends people off rotating keys that were never the problem.
Task to Setup: Quick Reference
| Task type | Model | max_tokens | Force tool use |
|---|---|---|---|
| Classify into fixed categories | claude-sonnet-5 | 256 | Yes |
| Extract fields from an email | claude-sonnet-5 | 512 | Yes |
| Extract from a long document | claude-sonnet-5 | 1024 | Yes |
| Route to a queue or owner | claude-sonnet-5 | 256 | Yes |
| Score or rank an item | claude-sonnet-5 | 256 | Yes |
| Sentiment plus reason | claude-sonnet-5 | 512 | Yes |
| Summarize for a human to read | claude-sonnet-5 | 1024 | No |
| Draft a reply for human review | claude-sonnet-5 | 2048 | No |
| Rewrite for tone | claude-sonnet-5 | 1024 | No |
| Multi-step reasoning on a hard case | claude-opus-5 | 4096 | Depends on output |
The pattern in that table is the whole article. If a node reads the output, force the tool. If a person reads it, do not bother.
A Working Order to Build This In
- Build the workflow with the built-in Anthropic node and confirm the trigger, the data in, and the data out all move correctly
- Swap in an HTTP Request node once you know what shape you need downstream
- Move the key into a Header Auth credential before you export or share anything
- Add the
toolsarray andtool_choiceand check the response has atool_useblock - Add the defensive Code node and an If node on
ok - Wire the false branch to yourself
- Log
usagefor a week, then tunemax_tokensagainst what you see
Steps 5 and 6 are the ones people skip, and they are the difference between a workflow you trust unattended and one you check every morning.
Where to Go From Here
The ideas that carry over to every other node are the same two: constrain the output with a schema rather than a prompt, and route failures to a person rather than letting them stop the run.
If you would rather start from something already wired up than build from an empty canvas, we keep a set of prebuilt n8n workflows with the Claude calls, schemas, and error branches already in place at clskillshub.com/automation-workflows.