n8n errors — offline reference

8 errors, each read from n8n's source. Saved from https://workflowerrors.com — free, no signup.


A 'json' property isn't an object

Code node · Code

You returned the right envelope — an item with a json key — but json points at something that isn't a plain object. n8n's description:

In the returned data, every key named 'json' must point to an object.

The identical check runs on binary, and produces A 'binary' property isn't an object. Everything below applies to both.

What n8n counts as "an object"

This is stricter than JavaScript's typeof, and the exact test is four conditions:

typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)

So all four of these fail, and three of them surprise people:

What you returned Why it fails
json: null typeof null === 'object', but n8n excludes it explicitly
json: [ … ] arrays are objects to JavaScript; n8n excludes them
json: new Date() a Date is an object; n8n excludes it
json: 'some string', json: 42, json: undefined not objects at all

The array case is the common one. { json: results } where results is an array of rows reads perfectly and is wrong — each row needs to be its own item:

// throws
return [{ json: rows }];

// right
return rows.map(row => ({ json: row }));

The Date case is the sharp one, because a Date inside json is fine. n8n walks the contents of json and stringifies anything that isn't a plain object — Dates, RegExps and the like — at any depth. So { json: { created: new Date() } } works and comes out as a string, while { json: new Date() } throws. The cleanup applies to what is inside the envelope, never to the envelope itself.

The undefined case, which looks like nothing at all

{ json: undefined } throws, and so does an item where a branch of your code simply never set json:

return $input.all().map(item => {
  const out = {};
  if (item.json.active) out.json = { id: item.json.id };
  return out;          // inactive items have no json at all
});

That's the same failure as json: undefined, and it's the one that shows up on item 40 of 50 rather than on the first item — because it's data-dependent. If this error names a late item index, look for a conditional that doesn't assign on every path.

return null is a different problem

Returning null as your whole result doesn't produce this message. It gets past the earlier shape checks (typeof null === 'object') and then fails inside n8n's item normalisation with a plain JavaScript type error rather than a validation message. If you're seeing a type error with no helpful description, check whether some path returns null instead of [].

Reading the bracketed suffix

Item 0 failing usually means the shape is wrong for every item. A late index almost always means a data-dependent branch, as above.

Why the envelope has to be an object

The json object is what the rest of n8n operates on: expressions resolve against its keys, the table view renders its columns, and downstream nodes address fields by name. An array has indices rather than names, a Date has neither, and null has nothing at all. None of them can carry the field access that every node after this one assumes.

Validating at the boundary is deliberate — the alternative is a workflow that runs another three nodes and then fails with an expression error pointing at code you didn't write.

Related errors


Source: validateItem and isObject in packages/nodes-base/nodes/Code/result-validation.ts and utils.ts, and standardizeOutput in the same utils.ts for the stringify-inside-json behaviour. Read directly from n8n's repository; the Code node documentation does not list these error strings.

Verified 16 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


Code doesn't return items properly

Code node · Code

Your Code node is in Run Once for All Items mode and returned something that isn't an array of objects. n8n checks the shape of the return value before it looks at any of your data, so this fires even when the data inside is perfect.

The description under it always reads: "Please return an array of objects, one for each item you would like to output."

Exactly when it fires

Two conditions, and only two. Both come straight from n8n's validator:

  1. The return value isn't an object at all. A string, a number, a boolean — or nothing, because a path through your code hit the end of the function without a return. That last one is the most common cause by a distance, and it's invisible when you read the code, because the return you're looking at is inside an if.

  2. You returned an array, but at least one element isn't an object. return [1, 2, 3] and return ['a', 'b'] both fail here. So does an array with a null hiding in it from a .map() that didn't cover every branch.

One consequence worth knowing: typeof null === 'object' in JavaScript, so return null passes this check and fails further down with a different message. If you're staring at this error, null is not what you returned.

The fix

The shape n8n wants is an array of objects, each with a json key:

return $input.all().map(item => ({
  json: {
    email: item.json.email,
    domain: item.json.email.split('@')[1],
  },
}));

Three checks that resolve almost every occurrence:

The wrapping rule nobody documents

n8n will wrap a bare object in json for you — but only under a specific condition, and getting it half-right is what produces the confusing failures.

n8n reserves exactly five top-level item keys:

json   binary   pairedItem   error   index

The rule:

So this is fine:

return [{ name: 'Ada', role: 'admin' }];      // wrapped for you

and this throws — not with the error at the top of this page, but with Invalid output format, described as "An output item contains the reserved key json. To get around this, please wrap each item in an object, under a key called json.":

return [{ json: { name: 'Ada' }, role: 'admin' }];   // mixed. refused.

The fix is to put the stray key where it belongs:

return [{ json: { name: 'Ada', role: 'admin' } }];

If no reserved key is present but an unknown one is flagged, the message is Unknown top-level item key: <yourKey>, described as "Access the properties of an item under .json, e.g. item.json" — same mistake, caught on a different path.

Wrong-mode errors look different, and that's useful

If you're in Run Once for Each Item mode, you get different wording, and the wording tells you which mistake you made:

Message You did this
Code doesn't return an object returned a string/number/undefined from per-item mode
Code doesn't return a single object returned an array from per-item mode

The second one's description says it outright: "If you need to output multiple items, please use the 'Run Once for All Items' mode instead." In per-item mode you return one bare object — return { json: { … } } — not an array of one.

So: if you're reading "Code doesn't return items properly", you are definitely in Run Once for All Items mode. That's already one thing narrowed down.

Reading the bracketed suffix

n8n appends location information to these messages when it has it:

So A 'json' property isn't an object [item 4] means item 4 specifically. Items 0–3 were fine, which usually means the failure is data-dependent — a field that's missing on some records, not a mistake in the shape of your code.

Related errors from the same node

Why this happens at all

The Code node sits on a boundary. Inside it you have ordinary JavaScript values; the moment you return, n8n needs items — objects in a fixed envelope that the rest of the workflow can carry, link and display. Nothing can translate an arbitrary JS value into that envelope unambiguously, so n8n validates instead of guessing, and throws at the boundary rather than letting a malformed item travel three nodes and fail somewhere that makes no sense.

The wrapping rule above is the one place n8n does guess — and it only guesses when your object gives it no reason to think you meant something else.


Sources: packages/nodes-base/nodes/Code/result-validation.ts, ValidationError.ts, JsCodeValidator.ts and reserved-key-found-error.ts in n8n's repository, read directly. n8n's Code node docs do not list these error strings.

Verified 15 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.

Self-hosting & infrastructure · Credentials

The instance you're running now is using a different encryption key than the one that encrypted those credential rows. The rows are intact and the database is fine — n8n simply cannot read them.

This almost always means one thing: the database was restored, moved or recreated without the key that went with it.

Before you try anything, understand the precedence rule

This is the part that wastes people's evenings. n8n gets its encryption key from one of two places, and they are not equal partners:

  1. If the settings file exists, the key in that file is used.

  2. Only if the settings file does not exist is N8N_ENCRYPTION_KEY used — and n8n then writes it into a new settings file.

  3. If the file exists and N8N_ENCRYPTION_KEY is set and they differ, n8n refuses to start:

    Mismatching encryption keys. The encryption key in the settings file <path> does not
    match the N8N_ENCRYPTION_KEY env var. Please make sure both keys match.
    

The settings file lives at config inside n8n's user folder — ~/.n8n/config by default. It's JSON, and the key is under encryptionKey.

So the reflex fix — "set N8N_ENCRYPTION_KEY to the old value and restart" — does nothing on an instance that already has a settings file, or it turns a runtime error into a container that won't boot. Neither outcome looks like progress and both are commonly reported as "setting the env var didn't work".

The fix, if you still have the old key

Find it first. It is one of:

Then make this instance use it, via whichever route matches how the instance is set up:

Restart, open any affected credential, and confirm the fields populate. That is the only proof that worked — the absence of an error on the credentials list page is not.

If the key is gone, the credentials are gone

There is no recovery path and no tool that can help. The secrets were encrypted with a key that no longer exists; that's the entire point of encrypting them.

What survives: the workflows, the credential records themselves, their names, types and every node's reference to them. What's lost: the secret values inside.

So the repair is to open each affected credential, re-enter the secrets, and save — which re-encrypts them under the current key. Tedious, proportional to how many credentials you have, and it does not require rebuilding any workflows.

Say it plainly to yourself before you spend three hours searching: nobody on a forum has a workaround for this, because there isn't one.

Why this happens, and the Docker version specifically

The encryption key is not stored in the database. If it were, it would be encrypting data while sitting next to it, which protects nothing. It lives on the instance's filesystem instead — which means the database and the key are two separate things to back up, and it is entirely possible, and very common, to back up only one.

The recurring version is a container whose ~/.n8n isn't on a named volume. n8n starts, finds no settings file, auto-generates a key (it logs "No encryption key found - Auto-generating and saving to: …"), and everything works. The container is later recreated — an image update, a host migration, a redeploy — the filesystem goes with it, n8n generates a new key, and now the database it reconnects to is full of rows it can no longer read. Nothing broke at the moment of failure; it broke at the moment the volume wasn't declared.

In queue mode, a worker started without an encryption key refuses to start rather than generating one of its own. That's deliberate: a worker that silently invented a key would write credential data the main instance couldn't read.

Prevent it in the two minutes you have now

Related credential errors

These come from the same place in n8n's source and are worth telling apart:

Message Meaning
Credentials could not be decrypted… wrong key — this page
Mismatching encryption keys… settings file and env var disagree; n8n won't start
No data is set on this credentials. the credential record has no encrypted payload at all
Decrypted credentials data is not valid JSON. decryption succeeded but produced garbage — usually partial or corrupted data, not a key problem
Credentials data is not in a valid format. the stored shape isn't what n8n expects

The fourth one is the one worth noticing: if decryption succeeded and the JSON is invalid, your key is correct and your data is damaged. That is a different problem with a different owner — your storage or your backup process, not your key.


Sources: packages/core/src/constants.ts (the exact message strings), packages/core/src/credentials.ts (where each is thrown) and packages/core/src/instance-settings/instance-settings.ts (the precedence rule and the mismatch error) in n8n's repository, read directly.

Verified 15 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


Invalid output format

Code node · Code

An item you returned has one of n8n's reserved keys and one of your own keys at the same level. n8n refuses that combination, because it can no longer tell whether the object is an n8n item or your data.

The description under it names the key it tripped on:

An output item contains the reserved key json. To get around this, please wrap each item in an object, under a key called json.

The five reserved keys

json   binary   pairedItem   error   index

That list is exact — it's a fixed set in n8n's source, not a convention. Any other top-level key is "yours".

The rule, stated properly

At the top level of an item, it's all or nothing:

So the failing shape is almost always someone who got the envelope right and then attached one extra thing to the outside of it:

// throws — 'json' is reserved, 'role' is yours
return [{ json: { name: 'Ada' }, role: 'admin' }];
// fine — role belongs inside
return [{ json: { name: 'Ada', role: 'admin' } }];

The same applies to binary. return [{ binary: { data: … }, filename: 'x.pdf' }] throws for exactly the same reason; the filename goes inside json, or inside the binary entry's own metadata.

Which key it names

n8n reports the first reserved key it encounters while walking the object's own keys, in insertion order. So an item built as { pairedItem: 0, json: {…}, note: 'x' } reports pairedItem, not json — even though json is the one you were thinking about. Don't read the named key as "the key that's wrong". Read it as "the key that proved this is meant to be an n8n item".

The one that's actually wrong is the unreserved one, and n8n doesn't name it here.

Reading the bracketed suffix

n8n appends location information when it has it:

An item index this far into the batch usually means the shape is data-dependent: some branch of your code builds the item differently from the others.

Why n8n refuses instead of guessing

An n8n item is a plain JavaScript object, and so is your data. There's no type tag to separate them. The only signal available is which keys are present — so n8n treats the presence of any reserved key as "this object is an envelope" and the presence of a foreign key as "this object is payload". An object claiming both is genuinely ambiguous, and every possible guess is wrong some of the time.

That's also why the no-reserved-keys case gets auto-wrapped without complaint: there's no ambiguity to resolve.

Related errors


Source: packages/nodes-base/nodes/Code/reserved-key-found-error.ts and result-validation.ts in n8n's repository, read directly. The reserved-key list is REQUIRED_N8N_ITEM_KEYS in that file. n8n's Code node documentation does not list this error string.

Verified 16 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


Model output doesn't fit required format

AI Agent & LangChain · Structured Output Parser

Your Structured Output Parser got text back from the model that either isn't valid JSON or doesn't match the schema you gave it — and if you built that schema with Generate from JSON Example, every field in your example is mandatory, so one missing key is enough to throw this.

Upgrading n8n will not fix it. This is thrown by design, and it's been reported across 1.88, 1.101, 1.107 and 1.112 on Claude, Gemini, GPT-4o and GPT-3.5 alike. The GitHub issue for it is closed as not planned.

First: find out which of three failures you have

The Structured Output Parser does three things in order, and the error message is identical whichever step fails. That's why the fixes people post in the forum contradict each other — they're fixing different bugs.

  1. Strip a markdown code fence, if there is one
  2. JSON.parse() whatever's left
  3. Validate the result against your schema (n8n uses Zod)

So before changing anything, read what the model actually sent. Two ways:

If it isn't JSON, you have Failure A. If it's JSON but a key is missing, spelled differently, or the wrong type, you have Failure B.

Failure A — it isn't JSON

n8n's fence stripping is stricter than people expect. It walks the reply line by line and only strips a fence when the opening line is exactly three backticks, or three backticks followed by json, with nothing else on that line — and the closing line is three backticks alone.

These all survive stripping and then blow up JSON.parse:

Prose before the opening fence is fine, and so is prose after the closing fence. It's the fence line itself that has to be clean.

The fix is in the prompt, not the parser. Put this in the system message, as the last line so it isn't buried:

Respond with a single JSON object and nothing else. No markdown, no code fences, no explanation before or after.

If your model node has a response-format option that forces JSON, turn it on as well — that removes the failure at the source instead of asking the model nicely.

Failure B — it's valid JSON, but the wrong shape

This is the common one, and it usually traces back to a single documented behaviour:

Generate from JSON Example treats every field in your example as required.

So if your example is

{ "company": "Acme", "amount": 1200, "note": "paid late" }

then the model must return note on every single item. On the run where there's nothing to note, it omits the key, Zod rejects the object, and you get this error — on item 40 of 50, which is why it looks intermittent.

Fix: switch Schema Type to Define using JSON Schema and write the schema by hand, putting only the genuinely mandatory keys in required:

{
  "type": "object",
  "properties": {
    "company": { "type": "string" },
    "amount":  { "type": "number" },
    "note":    { "type": "string" }
  },
  "required": ["company", "amount"]
}

Three more things that cause shape failures, in the order they're worth checking:

Failure C — check you're not reading the wrong error

If the message you actually got was "The AI model returned an empty response to the Structured Output Parser", that's a different error with a different cause: the model ran out of output tokens before it finished the object. Raise the model's max output tokens, shorten the prompt, or simplify the schema. Nothing above applies.

Why an Agent fails where a Chain succeeds

Several people in the main forum thread report the same prompt and the same schema working once they swap the AI Agent for a Basic LLM Chain. That isn't superstition. Under an Agent, the parser validates the agent's final answer, and an agent that has just run two tool calls has a strong habit of narrating what it did before answering. A chain has no tools and nothing to narrate.

If you don't need tool calling, use the chain. It's a smaller surface.

About the Auto-fixing Output Parser

The Auto-fixing Output Parser wraps another output parser, and when the first one fails it calls out to another LLM to repair the output. It's worth adding — but be clear about what it can and can't do:

So: fix the schema first, then add auto-fixing as a seatbelt. Not the other way round.

Why this happens at all

An LLM produces tokens, not objects. The Structured Output Parser is a validator bolted onto the end of a process that has no native concept of a schema, so the schema is enforced after the fact by throwing. Every design decision follows from that — which is why the durable fix is always to shrink the gap between what you ask for and what the model can reliably produce, rather than to catch failures harder.

Concretely, the workflows that don't hit this error are the ones that ask for a flat object, with a short list of required keys, with fixed-value fields declared as enums, and a system message that forbids anything but JSON.

Related errors

A worked example

A triage workflow I built applies both of the rules above to inbound email. The intent field is an enum of six fixed values rather than a free-text string, so the model cannot invent a seventh category on the one message that doesn't fit. And every company name and figure it extracts has to appear literally in the source message before anything downstream accepts it.

The enum removes the invented-value failure. The literal check removes the confidently-wrong one. Neither costs an extra model call, which is why they beat adding a repair pass on top.


Sources: n8n's source for the Structured Output Parser; the n8n docs for the Structured Output Parser and Auto-fixing Output Parser; n8n-io/n8n issue #18765; community.n8n.io thread 106424. n8n's own documentation does not currently cover this error.

Verified 15 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


Paired item data for item from node is unavailable. Ensure node is providing the required output.

Parameters & expressions · Expression / item linking

Your expression used .item to reach back to an earlier node, and somewhere in the chain between that node and this one, a node produced output without attaching pairedItem metadata — so n8n has no way to know which of the earlier node's items produced the item you're currently on.

If you just want it working: replace .item with .first(), .last() or .all()[index]. That skips item tracing entirely. The rest of this page is for deciding which of those is actually correct, and for the case where you need .item to work.

The exact string, and why yours looks different

n8n builds this message from a template, so the wording varies with what you wrote:

Paired item data for <method> from node '<NodeName>' is unavailable.
Ensure '<NodeName>' is providing the required output.

<method> is whichever of these you used — and it is the literal text that appears:

In the message What you wrote
item $('Node').item
itemMatching $('Node').itemMatching(i)
pairedItem the default, when n8n resolves linking internally
$getPairedItem internal resolution from a Code node

The item index is attached to the error as context rather than being part of the message itself, so depending on where you read the error — the node's red banner, the execution log, a forum paste — you may or may not see a trailing item number. Don't treat its absence as a different error.

Check this first: is the referenced node pinned?

If the node you're reaching back to has pinned data, n8n throws a completely different message for the same underlying problem:

Using the item method doesn't work with pinned data in this scenario.
Please unpin '<NodeName>' and try again.

and in some paths simply Unpin '<NodeName>' to execute.

This is worth checking before anything else, because it's the version that hits people mid-build — you pin some test data to stop re-hitting an API, and an expression that worked five minutes ago stops working. Unpin the node and the error goes. Nothing is wrong with your expression.

Fix 1 — use a method that doesn't need item linking

This is n8n's own recommendation and it's right most of the time. Which one you want depends on what the referenced node actually returns:

{{ $('Get Customer').first().json.email }}

If none of those is honest — if you genuinely need the item that corresponds to this one — then you need Fix 2.

Fix 2 — make the offending node supply the link

The culprit is almost always a Code node in "Run Once for All Items" mode. In that mode you build the output array yourself, and if you don't set pairedItem, nothing downstream can trace through your node.

Two things worth knowing, both from n8n's source:

So in "Run Once for All Items":

const items = $input.all();

return items.map((item, i) => ({
  json: { ...item.json, domain: item.json.email.split('@')[1] },
  pairedItem: { item: i },
}));

If your node fans out — one input item becoming several output items — point each output at the input it came from:

const out = [];
$input.all().forEach((item, i) => {
  for (const line of item.json.lines) {
    out.push({ json: line, pairedItem: { item: i } });
  }
});
return out;

n8n will also nudge you about this on its own: when a Code node returns a different number of items than it received, or any item without pairedItem, the output panel shows a hint reading "To make sure expressions after this node work, return the input items that produced each output item." That hint is not an error and is easy to scroll past, which is why most people meet it as this error instead, three nodes later.

Fix 3 — accept that some nodes can't supply it

A node that aggregates, merges, summarises or splits has genuinely destroyed the one-to-one relationship. There is no correct pairedItem for a row that was computed from forty input items. For anything downstream of such a node, .item is the wrong tool and Fix 1 is the answer, permanently.

The same failure, four other messages

This is the part that makes searching for it so frustrating: n8n's item-linking code throws five distinct messages, and it rewrites the message when the failure happens inside a Code node rather than in an expression field. So the same root cause reaches you under different text depending on where you hit it.

Expression field Inside a Code node What it means
Paired item data for … is unavailable (same) a node in the chain didn't supply pairedItem
Invalid expression / No path back to referenced node (same) the referenced node isn't wired to this one at all. Wire it up — intermediate nodes are fine
Branch not found / Paired item references non-existent branch Invalid branch reference a node pointed at an output branch that doesn't exist
Paired item resolution failed / Unable to find paired item source Data not found traversal ran out of chain before reaching the node
Multiple matches found / Multiple matching items for item [N] Multiple matches more than one item could be the source — genuinely ambiguous

Two neighbours that look similar but aren't item-linking problems at all:

Why this happens at all

n8n items don't carry their history inside them. Instead every node is expected to stamp each output item with a pointer to the input item that produced it, and .item resolves by walking that chain of pointers backwards, node by node, until it reaches the node you named. One node that doesn't stamp its output breaks the chain — and the error surfaces at the node doing the reading, which may be well downstream of the node actually at fault.

That's the practical lesson: the node named in the error is the node that failed to supply the data, not the node you need to edit. Read the node name in the message and go look at that node, not at the expression in front of you.

A worked example

A lead-reply workflow I built sidesteps this error rather than fixing it. Each branch — invalid address, unusable draft, mail-server rejection, successful send — writes its own outcome row with a reason at the point the outcome is known, instead of leaving an expression further downstream to trace back which lead a given result belonged to.

Nothing in it uses .item, so nothing in it can fail to resolve .item. That is usually the cheaper answer: when a workflow needs item linking in several places, the shape of the workflow is often what's wrong, not the expression.


Sources: packages/workflow/src/workflow-data-proxy.ts and the editor's English string table in n8n's repository, read directly. n8n documents item linking in general but does not have a page for this error string.

Verified 15 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


The requested webhook "POST /webhook/[id]" is not registered.

Webhooks · Webhook

Nothing is listening on that method and path at the moment your request arrived. n8n keeps a live registry of webhook routes, and a route is only in it while the workflow is active (production URL) or during the single call that follows clicking Execute workflow (test URL).

Don't read the message. Read the hint underneath it. n8n attaches one of exactly two hints, and which one you get tells you which URL n8n thinks you called — which is the whole diagnosis.

The two hints, verbatim

If the response hint says:

Click the 'Execute workflow' button on the canvas, then try again. (In test mode, the webhook only works for one call after you click this button)

…n8n received your request on the test URL. Test registration is armed by clicking Execute workflow and is consumed by one call. Not one minute, not one session — one call. If you clicked Execute, sent a request that failed for an unrelated reason, and sent a second one, the second gets this error.

If the hint says:

The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)

…n8n received your request on the production URL, and the workflow isn't active. Flip the toggle top-right.

That second hint also quietly answers the question people ask next: a successful production call shows nothing on the canvas. It's in Executions. A silent canvas is not evidence that the call failed.

If your path is right but your method is wrong, the message is different

n8n checks whether that path is registered for other HTTP methods first, and if it is, you get a different message entirely:

This webhook is not registered for POST requests. Did you mean to make a GET request?

If you're reading that, stop debugging activation — the workflow is active and the path is correct. You sent the wrong verb. (With several registered methods it lists them: Did you mean to make a GET, PUT or PATCH request?)

So the two messages split your problem in half before you touch anything:

What you see What's true
The requested webhook "…" is not registered. nothing is listening on that path at all
This webhook is not registered for X requests. Did you mean… the path is live; your HTTP method is wrong

"Received request for unknown webhook" is a log line, not an error

If you found that phrase in your server logs rather than in an HTTP response, it's n8n's own logging of this same failure:

Received request for unknown webhook: <the message above>

The useful part is everything after the colon — it's the message from the table above, including the path n8n actually received. On a self-hosted instance behind a reverse proxy that's the line worth reading, because a proxy that strips or rewrites a path prefix means n8n is being asked for a route you never typed. Compare the path in that log line against the URL you sent before assuming the workflow is at fault.

The checklist, in the order that resolves fastest

  1. Read the hint and act on whichever of the two it is. Most cases end here.
  2. Test vs production URL. They're different paths — the Webhook node shows both. Copying the test URL into a third-party service's settings and leaving it there is the single most common version of this error, because it works exactly once during setup and never again.
  3. Method. If you got the "did you mean" variant, this is it.
  4. Exact path. Trailing slashes, a stale UUID after the workflow was duplicated, an environment variable that still points at the old instance.
  5. Self-hosted, behind a proxy: compare the received path in the log line to what you sent.

Why it happens at all

The registry only holds routes that can currently be served. An inactive workflow has no listener — not a disabled one, no listener at all — so the HTTP layer answers 404 without ever consulting your workflow. That's why the error arrives instantly and why nothing appears in Executions: the request never reached an execution.

Test registration works the same way, with a deliberately tiny lifetime, because it exists to let you inspect one payload on the canvas, not to serve traffic. Treating the test URL as a real endpoint is the mistake the design invites and this error punishes.


Sources: packages/cli/src/errors/response-errors/webhook-not-found.error.ts and packages/cli/src/webhooks/webhook-request-handler.ts in n8n's repository, read directly. The hint text quoted above is n8n's own, verbatim from that source.

Verified 15 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).


Unknown top-level item key

Code node · Code

The named key isn't one of n8n's five reserved item keys, and this item has no reserved key of its own — but some other item in the array you returned does.

That last clause is the whole diagnosis, and it isn't in the message: this error means your array is inconsistent. Some items are correctly-formed n8n items and this one is a bare object. n8n's description tells you the fix without telling you the cause:

Access the properties of an item under .json, e.g. item.json

How it differs from "Invalid output format"

Both come out of the same check, three lines apart, and the difference is precise:

This item has… You get
a reserved key and a foreign key Invalid output format
a foreign key and no reserved key Unknown top-level item key: <key>

And the check only runs at all when at least one item somewhere in the array carries a reserved key. If no item does, n8n wraps every object in json for you and nothing is validated — which is why a uniformly bare array like return [{ a: 1 }, { a: 2 }] works perfectly.

So the shape that produces this error looks like this:

return $input.all().map(item => {
  if (item.json.skip) {
    return { json: { skipped: true } };   // an n8n item
  }
  return { name: item.json.name };        // NOT an n8n item
});

Both halves look reasonable in isolation. Together they're a mixed array, and the first branch is what switches validation on for the second.

The fix

Make every item the same shape. Pick the envelope and use it everywhere:

return $input.all().map(item => {
  if (item.json.skip) {
    return { json: { skipped: true } };
  }
  return { json: { name: item.json.name } };
});

If the inconsistency is coming from a .filter() or an early return inside a loop, the usual culprit is one path that forgot the wrapper. Search your function for every return and push and check they all produce the same keys.

It only happens in Run Once for All Items mode

This is worth knowing because it rules out half the places you might look.

In Run Once for Each Item mode you return a single object, and n8n normalises it before validating: a bare object gets wrapped in json first, so there's nothing foreign left at the top level by the time the check runs. The only way to fail in that mode is to hand over an object that already has json plus something else — and that produces Invalid output format, not this.

So if you're reading this message, you are in Run Once for All Items mode, and the problem is across your items rather than inside any one of them.

A near neighbour: "Inconsistent item format"

There's a narrow path where a mixed array slips past this check and fails one step later, inside n8n's normalisation helper, with:

Inconsistent item format

It happens when the non-json items carry only reserved keys — say [{ json: {…} }, { pairedItem: 0 }]. Nothing foreign is present, so the check above has nothing to complain about, and the normaliser refuses the mix instead. Same underlying mistake, different message, and the fix is identical: one shape for every item.

Why the check is conditional

n8n can't require the json envelope outright, because returning bare objects is a documented convenience and a lot of working code depends on it. So the envelope is optional — until you use it. The moment one item declares itself an n8n item, n8n stops guessing for the rest of the batch and starts enforcing.

That conditional behaviour is the reason this error can appear in a workflow that ran fine yesterday: add one branch that returns { json: … }, and every other branch is suddenly being held to a standard it wasn't before.

Related errors


Source: validateTopLevelKeys in packages/nodes-base/nodes/Code/result-validation.ts, and packages/core/src/execution-engine/node-execution-context/utils/normalize-items.ts for the normalisation order and the Inconsistent item format path. Read directly from n8n's repository; the Code node documentation does not list these error strings.

Verified 16 September 2026 against n8n 2.39.5 (source read on master @ 2.40.0).