| name | use-n8n |
| description | Use whenever you need to create, modify, execute, or debug n8n workflows — whether the n8n instance is self-hosted on Fly/Docker, n8n Cloud, or local. Triggers on intent phrases — "build an n8n workflow", "add a Telegram approve-gate", "ingest from Reddit/arXiv/HN via n8n", "import a workflow template", "n8n REST API", "n8n MCP", "self-host n8n on Fly", "n8n.fly.dev", or any task involving the n8n automation platform. Encodes the three operational tiers (n8n-mcp MCP server for best programmatic control / public REST API at /api/v1/ for direct curl / UI browser-automation as last resort), the workflow-template archive sources (Zie619 4,343-template repo + n8n.io/workflows official library), and the bug-class catalog of pitfalls (HTTP body double-stringify, /rest vs /api/v1, executeManually payload shape, code node language v2 quirk, session cookie 401, code node "metadata" reserved attr, etc.) that cost real session time when discovered through trial-and-error. |
use-n8n — programmatic n8n workflow control
n8n is the workflow-automation platform; this skill assumes a self-hosted instance, e.g. at https://your-n8n.example.com/ (self-hosted on Docker/Fly/whatever, n8n Cloud, or local all work the same way).
Multiple instances possible — route by project. The examples below are written for one self-hosted n8n instance. If you run additional n8n instances for other projects — e.g. another instance for a different project instead of this one — use that instance's own base URL and API-key env var (store credentials in a local, gitignored env file — never in the repo), and note any custom nodes or template libraries specific to it. The three operational tiers + the bug catalog below apply to ANY n8n instance; only the URL/creds differ.** This skill is what we know about driving it from Claude Code — built up after burning ~90 minutes of trial-and-error figuring out which API surface actually works.
TL;DR — three operational tiers
| Tier | When to use | Setup | Tool |
|---|
| A — n8n-mcp MCP server | Default for any non-trivial workflow work. Auto-fixes 7 common API quirks before they hit the API. Recommended for AI agents managing workflows. | Add r-ms/n8n-mcp to .mcp.json with N8N_API_URL + N8N_API_KEY env vars. Restart Claude Code. | mcp__n8n__* tools |
B — Public REST API at /api/v1/ | Quick scripted ops, smoke tests, importing templates. Uses X-N8N-API-KEY header. | curl / fetch with the PAT minted via Settings → API in the n8n UI | curl / fetch |
| C — UI browser-automation (claude-in-chrome) | Last resort for visual debugging. Slow but always works. | Already wired (claude-in-chrome MCP). Log in via your own credentials (stored in a local env file, never in the repo). | mcp__claude-in-chrome__* |
Critical: n8n has TWO REST APIs. Don't confuse them.
| API | Path prefix | Auth | When |
|---|
| PUBLIC | /api/v1/* | X-N8N-API-KEY header (PAT from Settings → API) | What you want 95% of the time. Documented at docs.n8n.io/api/. |
| INTERNAL (UI's own) | /rest/* | Session cookie (n8n-auth HttpOnly + browser-id header) | What n8n's web UI uses. Will silently no-op for some operations (e.g. /rest/workflows/:id/run returns status:success but doesn't actually fire the workflow nodes when payload shape is wrong). Don't use this from external scripts. |
Tier A — n8n-mcp MCP server (recommended)
r-ms/n8n-mcp is purpose-built for Claude Code managing n8n workflows. 20 tools across workflows / executions / credentials / node discovery. Auto-fix engine catches 7 common API quirks that trip up agents:
| Issue n8n-mcp auto-fixes | Without n8n-mcp this manifests as |
|---|
Code node v2 without language param | Workflow creates OK but errors at first run |
| Spaces in trigger node names | Webhooks break silently — POST returns 200 but nothing fires |
Missing settings field | Some operations fail with confusing "executionOrder undefined" |
ExecuteWorkflow workflowId as plain string | API silently accepts wrong shape, sub-workflow never runs |
MongoDB find without alwaysOutputData | Empty queries silently halt the workflow |
| SplitInBatches v3 wired to wrong output (0 vs 1) | Loop never iterates |
Missing webhookId on trigger nodes | Trigger doesn't register on activation |
Setup — add to project .mcp.json:
{
"mcpServers": {
"n8n": {
"type": "stdio",
"command": "node",
"args": ["/path/to/n8n-mcp/dist/index.js"],
"env": {
"N8N_API_URL": "https://your-n8n.example.com",
"N8N_API_KEY": "<paste-from-your-local-env-file>"
}
}
}
}
Then in any session, mcp__n8n__n8n_create_workflow, mcp__n8n__n8n_run_workflow, mcp__n8n__n8n_diagnose_workflow, etc. become callable.
Sibling project: czlonkowski/n8n-skills — 7 Claude Code skills (workflow patterns, JS/Python code nodes, expressions, validation, MCP-tool usage). Install via /plugin install czlonkowski/n8n-skills.
Tier B — Public REST API at /api/v1/
When you just need a one-shot curl or fetch and don't want the MCP server overhead.
Auth
- UI →
Settings → API → Create an API Key
- Copy the JWT-shaped token (shown ONCE)
- Save to a local, gitignored env file as
N8N_API_KEY=eyJ...
- Header:
X-N8N-API-KEY: $N8N_API_KEY on every request
Core endpoints
| Op | Method + Path | Body |
|---|
| List workflows | GET /api/v1/workflows | (query: active, limit, tags) |
| Get workflow | GET /api/v1/workflows/:id | — |
| Create workflow | POST /api/v1/workflows | {name, nodes, connections, settings: {executionOrder: "v1"}} |
| Update workflow | PUT /api/v1/workflows/:id | full object: {name, nodes, connections, settings} (strip read-only id/active/timestamps; PATCH also accepts the full object) |
| Edit a live Code node | GET /api/v1/workflows/:id → mutate the target node's parameters.jsCode/pythonCode in-memory → PUT the full object back | windowing/filtering logic edits with no UI + no re-import, e.g. narrowing a scheduled report node's query window to the last 48h with no UI access. |
| Delete workflow | DELETE /api/v1/workflows/:id | — |
| Activate | POST /api/v1/workflows/:id/activate | — |
| Deactivate | POST /api/v1/workflows/:id/deactivate | — |
| Execute (manual run) | POST /api/v1/workflows/:id/run | {workflowData: {full workflow object}, startNodes: ["Manual Trigger"]} |
| List executions | GET /api/v1/executions | (query: workflowId, status, limit, includeData=true) |
| Get execution | GET /api/v1/executions/:id?includeData=true | — |
Workflow JSON shape
Minimum viable workflow (4 nodes):
{
"name": "My workflow",
"nodes": [
{
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [240, 300],
"parameters": {}
},
{
"name": "RSS Feed",
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.1,
"position": [480, 300],
"parameters":
Common nodes reference
| Purpose | type | typeVersion |
|---|
| Manual trigger | n8n-nodes-base.manualTrigger | 1 |
| Schedule trigger (cron) | n8n-nodes-base.scheduleTrigger | 1.2 |
| Webhook trigger | n8n-nodes-base.webhook | 2 |
| RSS read | n8n-nodes-base.rssFeedRead | 1.1 |
| HTTP request | n8n-nodes-base.httpRequest | 4.2 |
| Code (JS or Python) | n8n-nodes-base.code | 2 (set language: "javaScript" OR "python") |
| Set/edit fields | n8n-nodes-base.set | 3.4 |
| If/conditional | n8n-nodes-base.if | 2 |
| Switch | n8n-nodes-base.switch | 3 |
| Merge | n8n-nodes-base.merge | 3 |
| Telegram approve-gate | n8n-nodes-base.telegram (operation sendAndWait) | 1.2 |
| Email approve-gate (any SMTP) | n8n-nodes-base.emailSend (operation sendAndWait) | 2.1 |
| Wait for webhook | n8n-nodes-base.wait | 1.1 |
| OpenRouter chat model | @n8n/n8n-nodes-langchain.lmChatOpenRouter | 1 |
| Anthropic chat model | @n8n/n8n-nodes-langchain.lmChatAnthropic | 1.3 |
| Gemini chat / image | @n8n/n8n-nodes-langchain.googleGemini | 1 (set model: "gemini-2.5-flash-image" for Nano Banana) |
| LangChain Agent | @n8n/n8n-nodes-langchain.agent | 2 |
Tier C — UI browser-automation (last resort)
When debugging visual state, inspecting individual node input/output panels, or doing one-off ops the API doesn't expose cleanly. Use claude-in-chrome MCP. Pattern:
navigate → claude-in-chrome MCP to https://your-n8n.example.com/workflow/<id>
find → "Execute workflow button" via natural-language ref lookup
left_click → fires the workflow
wait 5-10s → workflow runs
screenshot → visual confirmation; each node shows green check + item count
A claude-in-chrome UI session can inherit an already-logged-in browser session, when one is fresh. Sessions expire after a host restart — sign back in via Settings → Members using your own owner credentials (stored in a local, gitignored env file — never in the repo).
Workflow template archives — don't build from scratch
~4,343 community templates indexed and searchable:
- Zie619/n8n-workflows — Searchable archive at zie619.github.io/n8n-workflows.
git clone for offline grep. Path: workflows/<category>/<name>.json. Categories include AI / Slack / Notion / Reddit / Telegram / Discord / Email.
- n8n.io/workflows — Official template marketplace. Includes the "Summarize AI news from RSS, Reddit and HN with Claude" template (ID 13527) — perfect for AI-news ingestion + LLM drafting. ~$0.03-0.10/day Claude Haiku + Sonnet cost per the template author.
Import a template via UI
/home/workflows → top-right "Import from URL" → paste n8n.io workflow URL (e.g. https://n8n.io/workflows/13527/). n8n fetches + creates the workflow as draft.
Review a template before an opt-in API import
Never fetch a template from a mutable branch and send it directly to an
authenticated n8n endpoint. Resolve the desired upstream release/tag to its
immutable 40-hex commit first, then download it to a local review file with a
strict size and time bound:
set -u
umask 077
MAX_WORKFLOW_BYTES=1048576
WORKFLOW_REF="<40-hex-commit-for-the-reviewed-release>"
REVIEW_DIR=""
WORKFLOW_FILE=""
HEADER_FILE=""
REVIEW_COMPLETED=0
cleanup() {
rm -f -- "${HEADER_FILE:-}" "${WORKFLOW_FILE:-}"
if [[ -n "${REVIEW_DIR:-}" ]]; then
rmdir -- "${REVIEW_DIR:-}" 2>/dev/null || true
fi
}
on_signal() {
signal_status="$1"
cleanup
trap - EXIT HUP INT TERM
exit "$signal_status"
}
trap cleanup EXIT
trap 'on_signal 129' HUP
trap 'on_signal 130' INT
trap 'on_signal 143' TERM
REVIEW_DIR="$(mktemp -d "${TMPDIR:-/tmp}/n8n-review.XXXXXXXXXX")" || exit 1
WORKFLOW_FILE="$REVIEW_DIR/workflow.json"
if [[ ! "$WORKFLOW_REF" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "WORKFLOW_REF must be an immutable 40-hex commit" >&2
exit 2
CURL_VERSION=
CURL_VERSION=
CURL_VERSION=
[[ ! =~ ^([0-9]+)\.([0-9]+)(\.[0-9]+)?([.-].*)?$ ]];
>&2
2
CURL_MAJOR=
CURL_MINOR=
(( CURL_MAJOR < || (CURL_MAJOR == && CURL_MINOR < ) ));
>&2
2
! curl --fail --show-error --location \
--proto \
--tlsv1.2 \
--connect-timeout 10 \
--max-time 30 \
--max-filesize \
--output \
;
1
WORKFLOW_BYTES=
(( WORKFLOW_BYTES > MAX_WORKFLOW_BYTES ));
>&2
1
jq empty || {
1
}
! -- ;
>&2
1
REVIEW_COMPLETED=1
= || {
>&2
2
}
= || {
>&2
2
}
[[ -z ||
== *$* ||
== *$* ]];
>&2
2
HEADER_FILE= || 1
600
>
curl --fail --show-error --max-time 30 \
-X POST \
--header \
--header \
--data-binary
IMPORT_STATUS=
(( IMPORT_STATUS != ));
cleanup
- EXIT HUP INT TERM
Run the block in a dedicated Bash process so its traps own the temporary
artifacts. The human reviewer must inspect node code, URLs, credential
references, and workflow settings. The review file and secret-bearing header
file are removed on refusal, failure, signals, and successful import.
Imported workflows can reference credential IDs that do not exist in your
instance. During review, remove or re-bind those references; do not create or
attach credentials merely because an untrusted template names them.
Bug catalog — things that cost real time
These are the patterns I've burned cycles on. The skill exists primarily so future sessions don't repeat them.
B1 — HTTP body double-stringify (CRITICAL — silent fail)
Symptom: HTTP node shows green check + "1 item" output count, but downstream API returns 422 / silently doesn't write. n8n's UI does not surface the upstream error correctly when "Continue On Fail" defaults are involved.
Cause: jsonBody: '={{ JSON.stringify($json) }}' wraps the object in JSON-as-string. n8n's HTTP node THEN serializes that string again → server receives a quoted string, not a JSON object. FastAPI returns 422 "There was an error parsing the body".
Fix: Use jsonBody: '={{ $json }}' (returns the object directly; n8n stringifies once internally).
B2 — /rest/* silently no-op on /run
Symptom: POST /rest/workflows/:id/run returns {data: {executionId: "N"}} with status 200. Polling the execution shows status: "success" but runData: {}. No nodes actually fired.
Cause: The internal /rest/* API expects a specific executeManually payload shape that differs from documented. The error message you get when payload is malformed: executeManually was called with an unexpected payload (status 500). When payload is "almost right" the server silently completes with empty runData.
Fix: Use the PUBLIC /api/v1/workflows/:id/run endpoint instead. It accepts {workflowData: {...full workflow...}, startNodes: ["Manual Trigger"]} and actually fires nodes.
B3 — n8n session cookie not visible to fetch
Symptom: Calling /rest/* from in-browser JS with credentials: 'include' returns 401 even though UI is logged in.
Cause: n8n's auth cookie is HttpOnly (JavaScript can't see it). But fetch with credentials should still send it... except when machine was restarted (Fly secret change) the session expired between page load and fetch.
Fix: Either (a) refresh page first to mint new session OR (b) use the PUBLIC /api/v1/* API with the PAT header (more portable).
B4 — PAT extraction blocked by claude-in-chrome safety filter
Symptom: Reading the n8n PAT modal via JavaScript returns "[BLOCKED: JWT token]" or "[BLOCKED: Base64 encoded data]".
Cause: claude-in-chrome's safety filter redacts JWT-shaped tokens and base64-shaped blobs in output. Splitting into chunks doesn't help past ~36 chars.
Workarounds (3 in order of preference):
- Mint via UI then click Copy button — the value lands in OS clipboard. From there have a separate manual paste (operator involvement) OR use a clipboard-reading approach.
- Have JavaScript USE the PAT in-browser — never return it. Make all required n8n API calls from within the same JS execution block using
fetch('/api/v1/workflows', { headers: { 'X-N8N-API-KEY': pat } }). The PAT stays in JS scope only.
- Use the n8n-mcp MCP server instead — the PAT lives in
.mcp.json env which you configure once via Bash (not extracted via browser).
B5 — Empty runData in execution API responses
Symptom: GET /rest/executions/:id?includeData=true returns data.resultData.runData = {} even though UI canvas shows the nodes with green checks.
Cause: n8n's saveManualExecutions setting defaults to false. Manual runs complete but per-node data is discarded.
Fix: Set workflow.settings.saveManualExecutions = true AND saveExecutionProgress = true via PATCH before re-running. Or via UI: Workflow → Settings → "Save Execution Progress" + "Save Manual Executions".
B6 — Reddit RSS id shape needs flexible coercion
Symptom: n8n's RSS feed reader gives id as either a string (tag:reddit.com,2008:/r/MachineLearning/comments/<id>/) or an object (depending on feed). Normalizer that expects only string crashes.
Fix: Coerce: let externalId = typeof item.id === 'object' ? (item.id._ || item.id.guid || item.link) : (item.id || item.guid || item.link);. Truncate to slice(-128) so external_id stays within VARCHAR(256) limit.
B7 — n8n base image is Debian, not Alpine
Symptom: Dockerfile with apk add ... on n8nio/n8n:stable fails with /bin/sh: apk: not found.
Cause: n8n switched from Alpine to Debian-based images in recent releases.
Fix: Use apt-get install -y --no-install-recommends ... OR just drop the extra-package install (base image has curl + ca-certificates already).
B8 — n8n owner-setup wizard runs FIRST, ignores BASIC_AUTH env vars
Symptom: Set N8N_BASIC_AUTH_USER + N8N_BASIC_AUTH_PASSWORD Fly secrets, deploy n8n — but instance still shows owner-setup wizard instead of basic-auth prompt.
Cause: n8n 1.x has built-in user management that supersedes basic auth. The user-management wizard runs once, then you log in via the credentials it created.
Fix: Complete the owner-setup wizard via UI (or programmatically POST /rest/owner/setup). Save the owner credentials to your own local, gitignored env file (NOT the unused basic-auth ones).
Self-hosted deployment patterns
Self-hosting n8n (Docker / Fly / Railway / etc.)
- Any container platform works — pick whichever matches your existing infra.
- Give it a persistent volume for
n8n_data (workflows + credentials) — don't run it on ephemeral disk, or a redeploy wipes both.
- Owner credentials are created via n8n's first-boot owner-setup wizard; store them in your own local, gitignored env file — never in the repo.
- Cost/sizing is workload-dependent; watch out for aggressive auto-stop/auto-scale-to-zero settings that break webhook triggers waiting on an inbound request.
Example: an ingestion + drafting + approval pipeline
n8n is a good fit for a "poll sources → draft with an LLM → human approve-gate → publish" pipeline: hourly/cron trigger → multi-source RSS/API ingestion → an LLM chat-model node drafts a summary → a Telegram or email approve-gate (human-in-the-loop) → a POST to your own publish endpoint on approval. Wire any admin/API keys and LLM provider keys as n8n credentials sourced from your local env file — never hardcode them into the workflow JSON.
Deploying / updating a self-hosted instance
export FLY_API_TOKEN=$(grep "^access_token:" ~/.fly/config.yml | awk '{print $2}' | head -1)
flyctl deploy --remote-only --app <your-app> --yes
docker compose up -d --build
Most platform CLIs don't auto-read your local credential file in non-interactive shells — export the token into the shell env first.
Decision matrix: which tier for which task
| Task | Best tier | Why |
|---|
| Create a 4-node workflow from scratch | A (n8n-mcp) | Auto-fixes the language/settings/webhookId quirks |
| Import a template from n8n.io | B (review local JSON, then opt-in POST /api/v1/workflows) | Keeps download, inspection, and authenticated import separate |
| Bulk-import templates from Zie619 archive | B only after commit-pinned download and review | Do not bulk-import unreviewed remote JSON |
| Test-run a single workflow manually | A or C | A: n8n_run_workflow with timeout. C: UI button click. |
| Debug why a node returned empty data | C (UI) | n8n's UI shows per-node input/output in the side panel |
| Activate a webhook trigger | A or B (POST /api/v1/workflows/:id/activate) | Either works |
| Mint a new PAT | C (UI) — extract via in-browser JS that USES it directly (don't return value to chat) | Safety-filter blocks JWT extraction |
| Set up the OpenRouter LLM credential | B (POST /api/v1/credentials) | One POST with {name, type: "openRouterApi", data: {apiKey: "..."}} |
See also
Cross-references
- Built 2026-05-19 after ~90 min of trial-and-error figuring out which n8n API surface actually fires workflows. The first successful end-to-end ingest test confirmed the API surface.
- Skill exists primarily to spare future sessions the same trial-and-error.