n8n AI routing with a Needs review branch
Route n8n items with an LLM: one branch per answer, a Needs review branch for unsure ones, and a Feedback node that calibrates on your data.
For n8n AI routing that doesn't break on a stray full stop, use the n8n-nodes-curva community node and its Route operation. Each option you define becomes an output branch, answers below your confidence bar leave on a Needs review branch, and a Feedback node sends the person's answer back so Curva calibrates on your data. There is no text to parse and no Switch node to keep in sync. This guide walks through the setup, the exact branch layout, and the JSON each routed item carries.
In plain words. One Route node replaces an LLM node, a parser and a Switch: options become branches, unsure items go to a person, and the person's answer teaches the router.
The usual pattern is an LLM node that returns a word, followed by a Switch node that matches on it. It works until the model returns "Billing." with a full stop, or "billing / technical", or confidently picks a team for a message that fits none of them. The Route operation removes all three failure points, because the model's answer is mapped onto your option keys before n8n ever sees it.
Install n8n-nodes-curva and add the Curva API credential
You need a self-hosted n8n instance, because community nodes install there, and a running Curva server.
pip install curva-ai
export OPENROUTER_API_KEY=sk-or-v1-... # any OpenRouter key; free models workThen start the server with curva serve. It listens on http://localhost:7777 by default. If n8n runs in Docker and Curva on the host, n8n reaches it at http://host.docker.internal:7777. If you want the server to require a key, create one with curva keys create --name n8n; it prints a curva_… key once.
- In n8n, open Settings → Community Nodes → Install, enter
n8n-nodes-curva, and accept the notice. - For a manual install instead, run
npm install n8n-nodes-curvain~/.n8n/nodesand restart n8n. - Add a Curva API credential with the base URL and, if the server has keys, the
curva_…key.
The credential test calls GET /v1/models, which checks the key. The /health route never needs one, so it can't tell you whether the key works.
Build the n8n AI routing node: options become output branches
Add a Curva node after your trigger (a form, an email, a helpdesk webhook) and pick the Route operation.
- Question Type: Choice.
- Instructions: "Which team should handle this ticket?"
- Options:
billing("charges, refunds, invoices"),technical("bugs, errors, outages"),sales("pricing, quotes, upgrades"). The descriptions are what the model reads, so make them specific. - None of These: leave it on. It adds a
none_of_theseoption and branch, so a message that fits no team isn't forced into one. - Min Confidence: 0.8, the default.
- State: the whole item, some of its fields, or an expression such as
{{ $json.body }}.
The node's outputs now read billing, technical, sales, none_of_these and Needs review, in that order. They follow your options as you edit them: like n8n's Switch node, the node computes its outputs from its own parameters. Connect each branch to its queue, such as a helpdesk assignment, a Slack channel or a CRM task.
flowchart LR T["Trigger: form, email, webhook"] --> R["Curva Route node"] R -->|billing| B["Billing queue"] R -->|technical| C["Technical queue"] R -->|sales| S["Sales queue"] R -->|none_of_these| N["Triage inbox"] R -->|Needs review| H["Person decides"] H --> F["Curva Feedback node"]
What each routed item carries in its curva field
Every routed item keeps its data and gains a curva field:
{ "curva": { "id": "dec_…", "question": "department", "answer": "billing", "confidence": 0.93,
"probabilities": { "billing": 0.93, "technical": 0.04, "sales": 0.01, "none_of_these": 0.02 },
"calibrated": false, "abstain": false } }id is the decision id. Keep it on whatever record you create, because the Feedback step needs it. probabilities holds a value for every option, so a downstream node can see when two teams were close. calibrated turns true once there's enough feedback for that question. The node also adds model and cached, and rule when one of the question's rules answered it.
The Needs review branch: answers below Min Confidence 0.8
Anything below Min Confidence goes to Needs review, whatever the top answer was. That is Curva's abstain flag turned into a branch. Put a person there: a Slack message with buttons, an n8n form, or a task in your tracker.
For a yes/no router, pick Yes / No as the question type. The branches are Yes, No and Needs review. An answer is unsure when neither yes nor no reaches the bar. On this question type, answer is true or false and probabilities has a yes and a no value.
Choose the bar by what a wrong route costs. A misrouted sales lead is cheap to fix, so 0.8 may be fine. A refund sent to the wrong queue may need 0.9. Until the question is calibrated, the bar is a guess about the model. After calibration it means what it says.
Close the loop with a Feedback node
End the Needs review branch with another Curva node on Feedback:
- Decision ID defaults to
{{ $json.curva.id }}. - Question Key defaults to
{{ $json.curva.question }}. - True Label is the person's answer: an option key such as
billing,trueorfalsefor yes/no, or a level index for a score.
The node sends POST /v1/feedback. From 30 labels for a question in a project, Curva calibrates its confidences for your data whenever that makes them more accurate. From then on, a 0.8 confidence means about what it says on your items, and the Min Confidence bar sends the right share to people.
Send feedback for a sample of automatically routed items too, not only the reviewed ones. If only the unsure answers get labels, calibration never sees the confident ones. To see the effect, add a Curva node on Calibration Report for the question key and project. It returns labels, the fitted calibrator, and before and after accuracy, ECE, Brier score and reliability bins. The after numbers are held out, so they aren't flattered by testing on the labels the calibrator was fitted to.
Several questions per item with Decide
Route handles one question. When an item needs a team, an urgency score and a refund flag at once, use the Decide operation. It takes questions as JSON in the HTTP API format, sends them in one request, and attaches all answers to the item as curva.answers, exactly as the server returns them.
{
"team": {"type": "choice", "instructions": "Which team?",
"options": {"billing": "", "technical": "", "sales": ""}, "min_confidence": 0.8},
"urgency": {"type": "score", "instructions": "How urgent?",
"levels": ["low", "normal", "high", "critical"]},
"refund": {"type": "noul", "instructions": "The customer explicitly asks for a refund"}
}Route afterwards with an IF or Switch node on the team's choice, and send items whose team answer abstained to review. You can also paste a built-in recipe: curva recipe show support-triage prints a ready question set for tickets.
Model, project and options per node
- Model: a model id such as
@groq/qwen/qwen3.8-27b, or JSON for a fallback list, council or cascade. Empty means the server's default. - Project: a calibration namespace. Use one per workflow, so feedback from one workflow doesn't calibrate another.
- Options: config, debias, images, privacy and think.
When something fails, the node raises an n8n error carrying Curva's message: a 422 names the bad question, and a rate limit says when to retry. With Settings → On Error → Continue, the failed item goes to the last output (Needs review on Route) with an error field, so nothing is dropped silently.
Import the ticket-triage example
The docs include an example workflow, n8n-ticket-triage.json. Import it with Workflows → Import from File. Sample tickets go to a Route node with billing, technical, sales and none_of_these branches. Unsure tickets go to a review step, a Set node standing in for a Slack or form approval, that ends in a Feedback node. Pick your Curva credential on both Curva nodes and run it.
Next steps
Read the n8n guide in the docs and the node on npm. For the full triage build, see an n8n ticket triage workflow. For the review step itself, read n8n human in the loop AI. For a single yes/no gate, see an n8n AI yes/no filter, and for more ideas, LLM classification use cases.