<p><img class="alignnone size-full wp-image-4282" src="https://flowgenius.in/wp-content/uploads/2026/01/Child-4-Cluster-8.png" alt="n8n API key not recognized 401 fix guide" /></p>
<p style="text-align: center;">Complete fix guide for every n8n API key not recognized and 401 Unauthorized error</p>
<p> </p>
<hr />
<p> </p>
<p><!-- ============================================================ HUMAN HOOK — inserted before "Who this is for" ============================================================ --></p>
<p style="margin-bottom: 1.5em; line-height: 1.9; padding: 18px 22px; background: #f9f9f9; border-left: 4px solid #555; font-style: italic;">You generated the key, copied it carefully, pasted it into the header and n8n still throws 401. Most developers spend 40 minutes re-checking the key itself. The key is almost never the problem. The problem is <em>where</em> the key gets lost between your client and n8n’s auth middleware a trailing space, a proxy that strips custom headers, a container that never reloaded the <code>.env</code>, or an endpoint that simply doesn’t accept API key auth. This guide covers every scenario, with real error logs and the exact fix for each one.</p>
<p> </p>
<p> </p>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Who this is for:</strong> Developers and DevOps engineers who integrate with a self‑hosted or n8n Cloud instance via its REST API. <strong>We cover this in detail in the</strong> <a href="https://flowgenius.in/n8n-authentication-errors/">n8n Authentication Errors Guide</a>. If you receive <strong>“API key not recognized”</strong> from n8n, the request is missing a valid <code>x-n8n-api-key</code> header or the key stored in the environment variable is wrong.</p>
<p> </p>
<hr />
<p><!-- ============================================================ DEBUG DECISION TREE — inserted after intro, before Quick Fix ============================================================ --></p>
<h2 style="margin-bottom: 20px; line-height: 1.3;">Start Here: Debug Decision Tree</h2>
<p style="margin-bottom: 1.5em; line-height: 1.9;">Most devs get stuck because they assume all 401 errors are the same. They’re not. Find your exact branch below — each path leads to the right section so you don’t waste time chasing the wrong fix.</p>
<div style="background: #f4f4f4; border: 1px solid #ddd; border-radius: 6px; padding: 28px 32px; margin-bottom: 2em; font-family: monospace; font-size: 0.92em; line-height: 2;"><strong style="font-size: 1em; font-family: sans-serif;">You’re getting 401 “API key not recognized” – answer these questions:</p>
<p></strong><strong>Q1. Are you calling n8n’s OWN REST API (not an external service)?</strong><br />
→ YES → <strong>Q2 ↓</strong><br />
→ NO (OpenAI, ElevenLabs, etc. failing INSIDE n8n) → <strong>Jump to Section A</strong></p>
<p><strong>Q2. Does the exact same curl command work when hitting port 5678 directly?</strong><br />
→ YES (works direct, fails via domain/proxy) → <strong>Jump to Section 5 — Reverse Proxy</strong><br />
→ NO (fails even direct) → <strong>Q3 ↓</strong></p>
<p><strong>Q3. Did you restart n8n after setting the API key in .env?</strong><br />
→ NO → <strong>Restart the container, retry. Done.</strong><br />
→ YES → <strong>Q4 ↓</strong></p>
<p><strong>Q4. Are you calling <code>/rest/workflows</code> or <code>/api/v1/workflows</code>?</strong><br />
→ <code>/rest/</code> with x-n8n-api-key → <strong>Jump to Section B — Wrong Endpoint</strong><br />
→ <code>/api/v1/</code> with x-n8n-api-key → correct, go to <strong>Q5 ↓</strong></p>
<p><strong>Q5. Does the credential test show green in the UI but workflows still fail?</strong><br />
→ YES → <strong>Jump to Section C — Credential Test Passes but Execution Fails</strong><br />
→ NO → <strong>Q6 ↓</strong></p>
<p><strong>Q6. Did this start happening after a Docker restart, n8n update, or migration?</strong><br />
→ YES → <strong>Jump to Section D — Encryption Key Mismatch</strong><br />
→ NO → <strong>Work through Sections 1–4 in order (header, whitespace, key value)</strong></p>
</div>
<hr />
<h2></h2>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Quick fix</h2>
<ol style="margin-bottom: 2em; line-height: 1.9;">
<li>Verify the exact API key in <strong>Settings → API → API Key</strong> (or the <code>.env</code> file).</li>
<li>Ensure the request header is spelled <code>x-n8n-api-key</code> (case‑insensitive) and contains <strong>no extra spaces or line‑breaks</strong>.</li>
<li>Restart n8n after any change to the <code>.env</code> file.</li>
</ol>
<p style="margin-bottom: 2em; line-height: 1.9;">After these three steps the error should disappear.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">1. Why n8n Returns “API key not recognized”</h2>
<p><strong>If you encounter any</strong> <a href="/invalid-credentials-error">invalid credentials error</a><strong> resolve them before continuing with the setup.</strong></p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Root Cause</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">How n8n Detects It</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Missing header</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>req.headers['x-n8n-api-key']</code> is <code>undefined</code></td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Wrong header name</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Header key does not match <code>x-n8n-api-key</code> (e.g., <code>api-key</code>)</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Incorrect key value</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Header value ≠ <code>process.env.N8N_API_KEY</code></td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Trailing/leading whitespace</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">No <code>String.trim()</code> before comparison</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Key not loaded</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>.env</code> not re‑loaded after edit</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Multiple API keys (Enterprise)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Request uses a key not assigned to the calling user</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">Typical symptom for all cases: <strong>401 Unauthorized</strong> with message “API key not recognized”.</p>
<p style="margin-bottom: 2em; line-height: 1.9;">Understanding the exact cause lets you target the right fix without chasing dead‑ends.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">2. Locate the Correct API Key in n8n</h2>
<p><strong>If you encounter any</strong> <a href="/token-expired-error">token expired error</a><strong> resolve them before continuing with the setup.</strong></p>
<h3 style="margin-bottom: 40px; line-height: 1.3;">Self‑hosted (Docker / Binary)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Show the key stored in the .env file (default location)
cat /home/n8n/.n8n/.env | grep N8N_API_KEY</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">If the variable is missing, generate a new one:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Generate a 32‑byte hex string
openssl rand -hex 32
# Append it to the .env file
echo "N8N_API_KEY=9f2c3d4e5b6a7c8d9e0f1a2b3c4d5e6f" >> /home/n8n/.n8n/.env</pre>
<h3 style="margin-bottom: 40px; line-height: 1.3;">n8n Cloud</h3>
<ol style="margin-bottom: 2em; line-height: 1.9;">
<li>Open <strong>Settings → API → API Key</strong> in the UI.</li>
<li>Click <strong>Copy</strong>; the key lives in the cloud’s secret manager (you can only regenerate, not view again).</li>
</ol>
<blockquote style="margin: 2em 0; padding: 1em; border-left: 4px solid #e0e0e0; background: #fafafa;"><p><strong>EEFA note:</strong> Never commit the API key to source control. Use secret‑management tools (Docker secrets, Kubernetes <code>Secret</code>, or <code>.env</code> files excluded via <code>.gitignore</code>).</p></blockquote>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">3. Correctly Send the API Key in Your Request</h2>
<h3 style="margin-bottom: 40px; line-height: 1.3;">3.1 cURL Example</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">curl -X GET "https://your-n8n-instance.com/rest/workflows" \
-H "x-n8n-api-key: 9f2c3d4e5b6a7c8d9e0f1a2b3c4d5e6f" \
-H "Accept: application/json"</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Common pitfalls</strong> – see the table below.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Pitfall</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Symptom</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Double quotes around the key with trailing space</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Trim spaces: <code>x-n8n-api-key:$(echo -n "$KEY" | tr -d ' ')</code></td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Using <code>Authorization: Bearer <key></code></td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Switch to <code>x-n8n-api-key</code>.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Sending key in query string (<code>?api_key=</code>)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">n8n only reads the header; move it to header.</td>
</tr>
</tbody>
</table>
<h3 style="margin-bottom: 40px; line-height: 1.3;">3.2 JavaScript (Node.js) Example</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">require('dotenv').config(); // Load .env
const fetch = require('node-fetch');
const API_URL = 'https://your-n8n-instance.com/rest/workflows';
const API_KEY = process.env.N8N_API_KEY.trim(); // Trim whitespace</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Next, perform the request and handle errors:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">(async () => {
const res = await fetch(API_URL, {
method: 'GET',
headers: {
'x-n8n-api-key': API_KEY,
'Accept': 'application/json',
},
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Request failed (${res.status}): ${err}`);
}
const data = await res.json();
console.log(data);
})();</pre>
<blockquote style="margin: 2em 0; padding: 1em; border-left: 4px solid #e0e0e0; background: #fafafa;"><p><strong>EEFA tip:</strong> In production, wrap the request in a retry block and log the raw request/response <strong>without</strong> the API key (mask it) to avoid leaking credentials.</p></blockquote>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">4. Step‑by‑Step Troubleshooting Checklist</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Step</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Action</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Verification</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">1</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Confirm the key value – open <code>.env</code> or UI and copy it verbatim.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>echo $N8N_API_KEY</code> matches the UI copy.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">2</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Check header spelling – must be exactly <code>x-n8n-api-key</code>.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>curl -v</code> shows <code>> x-n8n-api-key:</code> line.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">3</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Trim whitespace – add <code>.trim()</code> in code or use <code>sed 's/ *$//'</code>.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">No trailing spaces in logs (<code>|</code> delimiters).</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">4</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Reload environment – restart Docker container or <code>pm2 restart n8n</code>.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>docker logs n8n | grep API_KEY</code> shows new value.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">5</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Test with cURL – bypass your app to isolate the issue.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Successful JSON response.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">6</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Inspect n8n logs – look for <code>API key not recognized</code> lines.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Log timestamp matches request.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">7</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Validate against multiple keys (Enterprise) – ensure the key belongs to the user.</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Use the UI to view assigned keys.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">8</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Check reverse proxy – ensure it forwards the <code>x-n8n-api-key</code> header (some proxies strip unknown headers).</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Add <code>proxy_set_header x-n8n-api-key $http_x_n8n_api_key;</code> in Nginx config.</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">If any step fails, correct the issue before moving to the next.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">5. Reverse Proxy Gotchas</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">Many production deployments sit behind <strong>NGINX</strong> or <strong>Traefik</strong>. By default, these proxies may drop custom headers.</p>
<h3 style="margin-bottom: 40px; line-height: 1.3;">NGINX Example</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Preserve the API key header
proxy_set_header x-n8n-api-key $http_x_n8n_api_key;
}</pre>
<h3 style="margin-bottom: 40px; line-height: 1.3;">Traefik (Docker) Example</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">labels:
- "traefik.http.middlewares.n8n-headers.headers.customrequestheaders.x-n8n-api-key=YOUR_KEY"
- "traefik.http.routers.n8n.middlewares=n8n-headers"</pre>
<blockquote style="margin: 2em 0; padding: 1em; border-left: 4px solid #e0e0e0; background: #fafafa;"><p><strong>EEFA warning:</strong> Hard‑coding the key in proxy config defeats the purpose of secret rotation. Prefer forwarding the original header instead of injecting a static value.</p></blockquote>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">6. Regenerating a Lost or Compromised API Key</h2>
<ol style="margin-bottom: 2em; line-height: 1.9;">
<li><strong>Self‑hosted</strong> – edit <code>.env</code> and replace <code>N8N_API_KEY</code> with a newly generated value (see Section 2).</li>
<li><strong>n8n Cloud</strong> – go to <strong>Settings → API → Regenerate</strong>; all existing clients must be updated instantly.</li>
</ol>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Production tip:</strong> Deploy a <strong>rolling key rotation</strong> script that updates the key, notifies dependent services via a webhook, and restarts containers one at a time to avoid downtime.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">7. When “API key not recognized” Persists After Fixes</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Symptom</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Likely Hidden Cause</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Remedy</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error despite correct header</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Proxy stripping header (see Section 5)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Verify <code>curl -v</code> through the proxy; add <code>proxy_set_header</code>.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error only for certain IPs</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">IP‑based firewall blocking header</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Whitelist the client IP or disable the rule.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error after Docker compose up</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>.env</code> file not mounted (volume missing)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Add <code>volumes: - ./n8n/.env:/home/node/.n8n/.env</code>.</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">401 error in CI pipeline</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">CI secret variable not exported</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Echo the variable in the CI step to confirm; use <code>export N8N_API_KEY=...</code>.</td>
</tr>
</tbody>
</table>
<p> </p>
<hr style="margin: 55px 0;" />
<p><!-- ============================================================ NEW SECTION A — External APIs failing inside n8n workflows ============================================================ --></p>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Section A: External API Keys Failing Inside n8n Workflows (OpenAI, ElevenLabs, fal.ai, Pinecone)</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">This is the most common real-world version of this error in 2025 — and it’s completely different from n8n’s own API key. You’re not calling n8n; you’re calling an external service <em>from inside</em> an n8n workflow, and that service returns 401. Here’s what the error looks like in the n8n execution log:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">{
"errorMessage": "Authorization failed - please check your credentials",
"errorDescription": "Request failed with status code 401",
"errorDetails": {
"rawErrorMessage": ["401 - \"{\\\"message\\\":\\\"Invalid API key\\\",\\\"statusCode\\\":401}\""],
"httpCode": "401"
},
"n8nDetails": {
"nodeName": "HTTP Request",
"nodeType": "n8n-nodes-base.httpRequest",
"n8nVersion": "1.108.1 (Self Hosted)"
}
}</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">If this is your error, the <code>x-n8n-api-key</code> header is irrelevant. The problem is how n8n is sending the external service’s credentials. Work through these fixes in order:</p>
<p> </p>
<hr />
<p> </p>
<h3 style="margin-bottom: 20px; line-height: 1.3;">A1. API key works in Postman/curl but fails inside n8n</h3>
<p style="margin-bottom: 1em; line-height: 1.9;">This is the single most-reported pattern across the n8n community. The key is valid — n8n is sending it wrong. Three causes in order of likelihood:</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Hidden characters from copy-paste.</strong> When you paste an API key into n8n’s credential field from a browser, an email, or a PDF, invisible Unicode characters (zero-width spaces, non-breaking spaces) tag along. The credential test endpoint is sometimes more lenient than the actual API call — so it passes the test but fails on execution.</p>
<p style="margin-bottom: 1em; line-height: 1.9;">Fix: delete the key from the credential field entirely, then type the key manually or paste it into a plain text editor first to strip invisible characters, then copy from there into n8n.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Wrong authentication method selected.</strong> In the HTTP Request node, the “Authentication” dropdown matters. If the API expects <code>Authorization: Bearer sk-xxx</code> but you’ve selected “Header Auth” with key name <code>Authorization</code> and value <code>sk-xxx</code> (without the word “Bearer”), it fails. The correct value would be <code>Bearer sk-xxx</code>.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Using a dedicated node vs. HTTP Request node.</strong> n8n’s dedicated OpenAI node, ElevenLabs node, etc. use their own authentication logic separate from the HTTP Request node. If one fails, try the other to isolate whether it’s a credential problem or a node implementation problem.</p>
<h3></h3>
<hr />
<h3></h3>
<h3 style="margin-bottom: 20px; line-height: 1.3;">A2. OpenAI sk-proj-… project-scoped keys not recognized</h3>
<p style="margin-bottom: 1em; line-height: 1.9;">OpenAI introduced project-scoped API keys (format: <code>sk-proj-...</code>) which require an additional <code>OpenAI-Project</code> header. n8n’s built-in OpenAI node does not fully support this format as of mid-2025. The result:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">Authorization failed - please check your credentials
Missing bearer or basic authentication in header</pre>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Fix option 1 (recommended):</strong> Use a classic user API key (<code>sk-...</code>) instead of a project key. In the OpenAI dashboard, go to API keys → create a new key without selecting a project.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Fix option 2:</strong> Use the HTTP Request node instead of the dedicated OpenAI node and pass both required headers manually:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">Authorization: Bearer sk-proj-YOUR_KEY
OpenAI-Project: proj_YOUR_PROJECT_ID
Content-Type: application/json</pre>
<h3></h3>
<hr />
<h3></h3>
<h3 style="margin-bottom: 20px; line-height: 1.3;">A3. Credential test shows green but execution returns 401</h3>
<p style="margin-bottom: 1em; line-height: 1.9;">This is the most confusing pattern — the credential test passes, your curl works, but the actual workflow execution returns 401. Reported across ElevenLabs, OpenRouter, fal.ai, and OpenAI nodes.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Why it happens:</strong> n8n’s credential test endpoint hits a simple “ping” endpoint of the external API (often just a GET to check if the key exists). The actual operation (e.g., text-to-speech, image generation) hits a different endpoint that may have scope restrictions, usage tier limits, or require a different auth header format.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Fix sequence:</strong></p>
<ol style="margin-bottom: 2em; line-height: 1.9;">
<li>Delete the existing credential in n8n completely.</li>
<li>Create a brand new credential from scratch — do not copy-paste from the old one.</li>
<li>Test by running the specific operation that was failing (not just the credential test).</li>
<li>If it still fails, test the exact same API call via curl with the same key to confirm the key has access to that specific operation/endpoint.</li>
<li>Check the external service’s dashboard — some APIs (ElevenLabs free tier, fal.ai) disable specific operations based on plan tier, which returns 401 even with a valid key.</li>
</ol>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Test ElevenLabs text-to-speech directly
curl -X POST https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM \
-H "xi-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "test", "model_id": "eleven_monolingual_v1"}'
# If this returns 401, the issue is with your key/plan, not n8n</pre>
<hr style="margin: 55px 0;" />
<p><!-- ============================================================ NEW SECTION B — Wrong endpoint confusion ============================================================ --></p>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Section B: Wrong Endpoint – /rest/ vs /api/v1/ Confusion</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">This catches a huge number of developers. n8n has two distinct API route structures and they behave differently with API key authentication:</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Endpoint prefix</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Purpose</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Auth method</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">API key works?</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>/api/v1/</code></td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Public REST API (workflows, executions, credentials)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>x-n8n-api-key</code> header</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">✅ Yes</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>/rest/</code></td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Internal editor API (used by the n8n UI itself)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Session cookie / browser auth</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">❌ No – returns 401</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">The <code>/rest/</code> endpoints are what n8n’s own browser UI uses. They require session-based authentication, not API key headers. If you’re hitting <code>/rest/workflows</code> with your API key, you’ll always get 401 regardless of how correct your key is.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Fix – use the correct public API path:</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># WRONG — internal endpoint, API key doesn't work here
curl -H "x-n8n-api-key: YOUR_KEY" http://localhost:5678/rest/workflows
# CORRECT — public API endpoint
curl -H "x-n8n-api-key: YOUR_KEY" http://localhost:5678/api/v1/workflows</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>For self-referencing n8n API credentials</strong> (when n8n calls its own API inside a workflow): the Base URL must be your n8n instance’s <em>external</em> URL, not <code>localhost</code> – because n8n inside Docker doesn’t reach itself via localhost. Use the container name or external domain:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># If n8n is in Docker, use the container name or service name from docker-compose
Base URL: http://n8n:5678/api/v1
# NOT: http://localhost:5678/api/v1 ← this resolves inside the container, not to n8n</pre>
<hr style="margin: 55px 0;" />
<p><!-- ============================================================ NEW SECTION C — JWT timestamp bug ============================================================ --></p>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Section C: JWT Timestamp Bug – Fresh API Key Returns 401 Immediately</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">This one is genuinely surprising. You generate a brand new API key from the n8n UI, use it immediately — and get 401. You’ve verified the header, the endpoint, everything. The key is completely fresh. It still fails.</p>
<p style="margin-bottom: 2em; line-height: 1.9;">n8n API keys are JWT tokens. A confirmed bug (reported October 2025, GitHub issue #21054) causes the JWT’s <code>iat</code> (issued-at) timestamp to be set to a <em>future</em> timestamp — typically 6–7 minutes ahead of the actual current time. JWT validation rejects tokens whose <code>iat</code> is in the future, because it means “this token hasn’t been issued yet.” The result is a 401 that has nothing to do with your key value.</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Decode your API key (it starts with eyJ...) to check the iat field
echo "YOUR_JWT_API_KEY" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
# Look for: "iat": 1761135964
# Convert to human time: date -d @1761135964
# If iat is in the future, you've hit this bug</pre>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Fix 1: wait 10 minutes.</strong> Once the server clock catches up to the <code>iat</code> timestamp, the key becomes valid automatically. This is the easiest workaround.</p>
<p style="margin-bottom: 1em; line-height: 1.9;"><strong>Fix 2: sync the container clock.</strong> The root cause is clock drift between the Docker container and the host. Fix it:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Check time inside the n8n container
docker exec -it n8n date
# If it differs from host time, sync the host NTP
sudo timedatectl set-ntp true
# Then restart the container
docker restart n8n</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Fix 3: regenerate the key after the clock is synced.</strong> The existing key’s <code>iat</code> is baked in — you must generate a new key after fixing the clock drift for the timestamp to be correct.</p>
<hr style="margin: 55px 0;" />
<p><!-- ============================================================ NEW SECTION D — Encryption key mismatch ============================================================ --></p>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Section D: N8N_ENCRYPTION_KEY Mismatch: Credentials Silently Break After Update or Restart</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">This is the highest-impact, least-documented cause of “API key not recognized” in production. After a Docker container rebuild, an n8n version update, or a server migration, every stored credential silently stops working. The error in the execution log looks identical to a wrong API key:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">Authorization failed - please check your credentials
# or
Credentials could not be decrypted.
The likely reason is that a different 'encryptionKey' was used to encrypt the data.</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>What’s happening:</strong> n8n encrypts all stored credentials (API keys, passwords, OAuth tokens) using a key defined by <code>N8N_ENCRYPTION_KEY</code>. On first launch, if you don’t set this variable explicitly, n8n generates a random key and saves it to <code>~/.n8n/config</code>. If that file disappears — because you rebuilt the Docker volume, migrated servers, or forgot to persist the <code>.n8n</code> directory — n8n generates a <em>new</em> random key. It can no longer decrypt the credentials stored with the old key. Every workflow that touches any credential will return 401 or “credentials could not be decrypted.”</p>
<h3 style="margin-bottom: 20px; line-height: 1.3;">How to prevent this? (do this before it happens)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Generate a stable encryption key and save it NOW
openssl rand -hex 32
# Add to your .env file
N8N_ENCRYPTION_KEY=your_generated_64_char_hex_string
# In docker-compose.yml, set it explicitly
environment:
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}</pre>
<h3 style="margin-bottom: 20px; line-height: 1.3;">How to recover if it already happened?</h3>
<ol style="margin-bottom: 2em; line-height: 1.9;">
<li>Find the old encryption key. Check <code>~/.n8n/config</code> on the original host — it’s stored there as <code>"encryptionKey": "..."</code>. If you have a backup of the <code>.n8n</code> directory, extract it from there.</li>
<li>Set that exact key as <code>N8N_ENCRYPTION_KEY</code> in your environment and restart n8n.</li>
<li>If the old key is completely lost, there is no way to decrypt the stored credentials. You must recreate all credentials from scratch.</li>
</ol>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Find the encryption key from a running container's config
docker exec -it n8n cat /home/node/.n8n/config | grep encryptionKey
# Or from a backed-up .n8n directory
grep encryptionKey /path/to/backup/.n8n/config</pre>
<blockquote style="margin: 2em 0; padding: 1em; border-left: 4px solid #e0e0e0; background: #fafafa;"><p><strong>EEFA note:</strong> In queue mode (multiple n8n workers), every worker and the main instance must load the <em>identical</em> <code>N8N_ENCRYPTION_KEY</code> value. If even one worker has a different key, credentials will silently fail on that worker’s executions but work fine on others — making this extremely hard to diagnose.</p></blockquote>
<hr style="margin: 55px 0;" />
<p><!-- ============================================================ NEW SECTION E — Real log reading guide ============================================================ --></p>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Section E: Reading Real n8n Logs to Diagnose API Key Errors</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">Most developers check the n8n UI execution error and stop there. The real diagnostic information is in the container logs. Here’s how to read them and what to look for:</p>
<h3 style="margin-bottom: 20px; line-height: 1.3;">Enable verbose logging</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Add to your .env or docker-compose environment
N8N_LOG_LEVEL=debug
# Tail live logs
docker logs -f n8n 2>&1 | grep -i "api\|auth\|401\|key\|credential"</pre>
<h3 style="margin-bottom: 20px; line-height: 1.3;">What each log pattern means?</h3>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Log line</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">What it means</th>
<th style="border: 1px solid #e0e0e0; padding: 13px;">Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>API key not recognized</code></td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Header received but value doesn’t match stored key</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Sections 1–4: verify key value, whitespace, reload env</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>Unauthorized</code> with no additional message</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Header is missing entirely — never reached auth middleware</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Section 5: proxy stripping header, or wrong endpoint (Section B)</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>Credentials could not be decrypted</code></td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Encryption key mismatch after restart/migration</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Section D: recover or reset N8N_ENCRYPTION_KEY</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>Request failed with status code 401</code> inside a workflow node</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">External API rejecting the credential n8n is sending</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Section A: hidden characters, wrong auth method, plan limits</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><code>iat set to future timestamp</code> or token rejected immediately</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">JWT clock drift bug</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Section C: sync container clock, wait 10 min, regenerate key</td>
</tr>
</tbody>
</table>
<hr style="margin: 55px 0;" />
<p><!-- ============================================================ NEW SECTION F — Verify your API key works ============================================================ --></p>
<h2 style="margin-bottom: 45px; line-height: 1.3;">Section F: Verify the Fix: Confirm Your API Key Is Working</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">After applying any fix, don’t assume it worked – verify it with a direct test before updating your application code.</p>
<h3 style="margin-bottom: 20px; line-height: 1.3;">Minimal verification curl</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Replace with your actual instance URL and key
curl -s -o /dev/null -w "%{http_code}" \
-H "x-n8n-api-key: YOUR_KEY" \
https://your-n8n-instance.com/api/v1/workflows
# Expected output: 200
# If you see 401: key is wrong or not loaded
# If you see 404: you're hitting the wrong endpoint path</pre>
<h3 style="margin-bottom: 20px; line-height: 1.3;">Verbose debug to see exactly what’s being sent</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;">curl -v \
-H "x-n8n-api-key: YOUR_KEY" \
https://your-n8n-instance.com/api/v1/workflows 2>&1 | grep -E "^[><]"
# Look for this line in output:
# > x-n8n-api-key: YOUR_KEY
# If that line is missing, your client isn't sending the header at all</pre>
<h3 style="margin-bottom: 20px; line-height: 1.3;">Test through the proxy specifically</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto;"># Test DIRECT (bypassing proxy)
curl -H "x-n8n-api-key: YOUR_KEY" http://localhost:5678/api/v1/workflows
# Test VIA PROXY (through your domain)
curl -H "x-n8n-api-key: YOUR_KEY" https://n8n.yourdomain.com/api/v1/workflows
# If direct works but proxy fails → Section 5 (proxy stripping header)</pre>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">9. Next Steps</h2>
<ul style="margin-bottom: 2em; line-height: 1.9;">
<li><strong>Securely store API keys</strong> – explore Vault, AWS Secrets Manager, or Docker secrets.</li>
<li><strong>Implement request retries with exponential back‑off</strong> for transient 401 errors.</li>
<li><strong>Monitor authentication failures</strong> via Grafana or Prometheus alerts.</li>
</ul>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">Conclusion</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">The “API key not recognized” error is almost always a mismatch between the key stored in n8n and the header sent by the client. By verifying the exact key, ensuring the <code>x-n8n-api-key</code> header is correctly spelled and whitespace‑free, and restarting the service after any environment change, the issue disappears. Remember to forward the header through any reverse proxy and keep keys out of source control. Following the checklist and the proxy‑preservation tips guarantees reliable, production‑grade API access to n8n.</p>

Complete fix guide for every n8n API key not recognized and 401 Unauthorized error
You generated the key, copied it carefully, pasted it into the header and n8n still throws 401. Most developers spend 40 minutes re-checking the key itself. The key is almost never the problem. The problem is where the key gets lost between your client and n8n’s auth middleware a trailing space, a proxy that strips custom headers, a container that never reloaded the .env, or an endpoint that simply doesn’t accept API key auth. This guide covers every scenario, with real error logs and the exact fix for each one.
Who this is for: Developers and DevOps engineers who integrate with a self‑hosted or n8n Cloud instance via its REST API. We cover this in detail in the n8n Authentication Errors Guide. If you receive “API key not recognized” from n8n, the request is missing a valid x-n8n-api-key header or the key stored in the environment variable is wrong.
Start Here: Debug Decision Tree
Most devs get stuck because they assume all 401 errors are the same. They’re not. Find your exact branch below — each path leads to the right section so you don’t waste time chasing the wrong fix.
You’re getting 401 “API key not recognized” – answer these questions:
Q1. Are you calling n8n’s OWN REST API (not an external service)?
→ YES →
Q2 ↓
→ NO (OpenAI, ElevenLabs, etc. failing INSIDE n8n) →
Jump to Section A
Q2. Does the exact same curl command work when hitting port 5678 directly?
→ YES (works direct, fails via domain/proxy) → Jump to Section 5 — Reverse Proxy
→ NO (fails even direct) → Q3 ↓
Q3. Did you restart n8n after setting the API key in .env?
→ NO → Restart the container, retry. Done.
→ YES → Q4 ↓
Q4. Are you calling /rest/workflows or /api/v1/workflows?
→ /rest/ with x-n8n-api-key → Jump to Section B — Wrong Endpoint
→ /api/v1/ with x-n8n-api-key → correct, go to Q5 ↓
Q5. Does the credential test show green in the UI but workflows still fail?
→ YES → Jump to Section C — Credential Test Passes but Execution Fails
→ NO → Q6 ↓
Q6. Did this start happening after a Docker restart, n8n update, or migration?
→ YES → Jump to Section D — Encryption Key Mismatch
→ NO → Work through Sections 1–4 in order (header, whitespace, key value)
Quick fix
- Verify the exact API key in Settings → API → API Key (or the
.env file).
- Ensure the request header is spelled
x-n8n-api-key (case‑insensitive) and contains no extra spaces or line‑breaks.
- Restart n8n after any change to the
.env file.
After these three steps the error should disappear.
1. Why n8n Returns “API key not recognized”
If you encounter any invalid credentials error resolve them before continuing with the setup.
| Root Cause |
How n8n Detects It |
| Missing header |
req.headers['x-n8n-api-key'] is undefined |
| Wrong header name |
Header key does not match x-n8n-api-key (e.g., api-key) |
| Incorrect key value |
Header value ≠ process.env.N8N_API_KEY |
| Trailing/leading whitespace |
No String.trim() before comparison |
| Key not loaded |
.env not re‑loaded after edit |
| Multiple API keys (Enterprise) |
Request uses a key not assigned to the calling user |
Typical symptom for all cases: 401 Unauthorized with message “API key not recognized”.
Understanding the exact cause lets you target the right fix without chasing dead‑ends.
2. Locate the Correct API Key in n8n
If you encounter any token expired error resolve them before continuing with the setup.
Self‑hosted (Docker / Binary)
# Show the key stored in the .env file (default location)
cat /home/n8n/.n8n/.env | grep N8N_API_KEY
If the variable is missing, generate a new one:
# Generate a 32‑byte hex string
openssl rand -hex 32
# Append it to the .env file
echo "N8N_API_KEY=9f2c3d4e5b6a7c8d9e0f1a2b3c4d5e6f" >> /home/n8n/.n8n/.env
n8n Cloud
- Open Settings → API → API Key in the UI.
- Click Copy; the key lives in the cloud’s secret manager (you can only regenerate, not view again).
EEFA note: Never commit the API key to source control. Use secret‑management tools (Docker secrets, Kubernetes Secret, or .env files excluded via .gitignore).
3. Correctly Send the API Key in Your Request
3.1 cURL Example
curl -X GET "https://your-n8n-instance.com/rest/workflows" \
-H "x-n8n-api-key: 9f2c3d4e5b6a7c8d9e0f1a2b3c4d5e6f" \
-H "Accept: application/json"
Common pitfalls – see the table below.
| Pitfall |
Symptom |
Fix |
| Double quotes around the key with trailing space |
401 error |
Trim spaces: x-n8n-api-key:$(echo -n "$KEY" | tr -d ' ') |
Using Authorization: Bearer <key> |
401 error |
Switch to x-n8n-api-key. |
Sending key in query string (?api_key=) |
401 error |
n8n only reads the header; move it to header. |
3.2 JavaScript (Node.js) Example
require('dotenv').config(); // Load .env
const fetch = require('node-fetch');
const API_URL = 'https://your-n8n-instance.com/rest/workflows';
const API_KEY = process.env.N8N_API_KEY.trim(); // Trim whitespace
Next, perform the request and handle errors:
(async () => {
const res = await fetch(API_URL, {
method: 'GET',
headers: {
'x-n8n-api-key': API_KEY,
'Accept': 'application/json',
},
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Request failed (${res.status}): ${err}`);
}
const data = await res.json();
console.log(data);
})();
EEFA tip: In production, wrap the request in a retry block and log the raw request/response without the API key (mask it) to avoid leaking credentials.
4. Step‑by‑Step Troubleshooting Checklist
| Step |
Action |
Verification |
| 1 |
Confirm the key value – open .env or UI and copy it verbatim. |
echo $N8N_API_KEY matches the UI copy. |
| 2 |
Check header spelling – must be exactly x-n8n-api-key. |
curl -v shows > x-n8n-api-key: line. |
| 3 |
Trim whitespace – add .trim() in code or use sed 's/ *$//'. |
No trailing spaces in logs (| delimiters). |
| 4 |
Reload environment – restart Docker container or pm2 restart n8n. |
docker logs n8n | grep API_KEY shows new value. |
| 5 |
Test with cURL – bypass your app to isolate the issue. |
Successful JSON response. |
| 6 |
Inspect n8n logs – look for API key not recognized lines. |
Log timestamp matches request. |
| 7 |
Validate against multiple keys (Enterprise) – ensure the key belongs to the user. |
Use the UI to view assigned keys. |
| 8 |
Check reverse proxy – ensure it forwards the x-n8n-api-key header (some proxies strip unknown headers). |
Add proxy_set_header x-n8n-api-key $http_x_n8n_api_key; in Nginx config. |
If any step fails, correct the issue before moving to the next.
5. Reverse Proxy Gotchas
Many production deployments sit behind NGINX or Traefik. By default, these proxies may drop custom headers.
NGINX Example
location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Preserve the API key header
proxy_set_header x-n8n-api-key $http_x_n8n_api_key;
}
Traefik (Docker) Example
labels:
- "traefik.http.middlewares.n8n-headers.headers.customrequestheaders.x-n8n-api-key=YOUR_KEY"
- "traefik.http.routers.n8n.middlewares=n8n-headers"
EEFA warning: Hard‑coding the key in proxy config defeats the purpose of secret rotation. Prefer forwarding the original header instead of injecting a static value.
6. Regenerating a Lost or Compromised API Key
- Self‑hosted – edit
.env and replace N8N_API_KEY with a newly generated value (see Section 2).
- n8n Cloud – go to Settings → API → Regenerate; all existing clients must be updated instantly.
Production tip: Deploy a rolling key rotation script that updates the key, notifies dependent services via a webhook, and restarts containers one at a time to avoid downtime.
7. When “API key not recognized” Persists After Fixes
| Symptom |
Likely Hidden Cause |
Remedy |
| 401 error despite correct header |
Proxy stripping header (see Section 5) |
Verify curl -v through the proxy; add proxy_set_header. |
| 401 error only for certain IPs |
IP‑based firewall blocking header |
Whitelist the client IP or disable the rule. |
| 401 error after Docker compose up |
.env file not mounted (volume missing) |
Add volumes: - ./n8n/.env:/home/node/.n8n/.env. |
| 401 error in CI pipeline |
CI secret variable not exported |
Echo the variable in the CI step to confirm; use export N8N_API_KEY=.... |
Section A: External API Keys Failing Inside n8n Workflows (OpenAI, ElevenLabs, fal.ai, Pinecone)
This is the most common real-world version of this error in 2025 — and it’s completely different from n8n’s own API key. You’re not calling n8n; you’re calling an external service from inside an n8n workflow, and that service returns 401. Here’s what the error looks like in the n8n execution log:
{
"errorMessage": "Authorization failed - please check your credentials",
"errorDescription": "Request failed with status code 401",
"errorDetails": {
"rawErrorMessage": ["401 - \"{\\\"message\\\":\\\"Invalid API key\\\",\\\"statusCode\\\":401}\""],
"httpCode": "401"
},
"n8nDetails": {
"nodeName": "HTTP Request",
"nodeType": "n8n-nodes-base.httpRequest",
"n8nVersion": "1.108.1 (Self Hosted)"
}
}
If this is your error, the x-n8n-api-key header is irrelevant. The problem is how n8n is sending the external service’s credentials. Work through these fixes in order:
A1. API key works in Postman/curl but fails inside n8n
This is the single most-reported pattern across the n8n community. The key is valid — n8n is sending it wrong. Three causes in order of likelihood:
Hidden characters from copy-paste. When you paste an API key into n8n’s credential field from a browser, an email, or a PDF, invisible Unicode characters (zero-width spaces, non-breaking spaces) tag along. The credential test endpoint is sometimes more lenient than the actual API call — so it passes the test but fails on execution.
Fix: delete the key from the credential field entirely, then type the key manually or paste it into a plain text editor first to strip invisible characters, then copy from there into n8n.
Wrong authentication method selected. In the HTTP Request node, the “Authentication” dropdown matters. If the API expects Authorization: Bearer sk-xxx but you’ve selected “Header Auth” with key name Authorization and value sk-xxx (without the word “Bearer”), it fails. The correct value would be Bearer sk-xxx.
Using a dedicated node vs. HTTP Request node. n8n’s dedicated OpenAI node, ElevenLabs node, etc. use their own authentication logic separate from the HTTP Request node. If one fails, try the other to isolate whether it’s a credential problem or a node implementation problem.
A2. OpenAI sk-proj-… project-scoped keys not recognized
OpenAI introduced project-scoped API keys (format: sk-proj-...) which require an additional OpenAI-Project header. n8n’s built-in OpenAI node does not fully support this format as of mid-2025. The result:
Authorization failed - please check your credentials
Missing bearer or basic authentication in header
Fix option 1 (recommended): Use a classic user API key (sk-...) instead of a project key. In the OpenAI dashboard, go to API keys → create a new key without selecting a project.
Fix option 2: Use the HTTP Request node instead of the dedicated OpenAI node and pass both required headers manually:
Authorization: Bearer sk-proj-YOUR_KEY
OpenAI-Project: proj_YOUR_PROJECT_ID
Content-Type: application/json
A3. Credential test shows green but execution returns 401
This is the most confusing pattern — the credential test passes, your curl works, but the actual workflow execution returns 401. Reported across ElevenLabs, OpenRouter, fal.ai, and OpenAI nodes.
Why it happens: n8n’s credential test endpoint hits a simple “ping” endpoint of the external API (often just a GET to check if the key exists). The actual operation (e.g., text-to-speech, image generation) hits a different endpoint that may have scope restrictions, usage tier limits, or require a different auth header format.
Fix sequence:
- Delete the existing credential in n8n completely.
- Create a brand new credential from scratch — do not copy-paste from the old one.
- Test by running the specific operation that was failing (not just the credential test).
- If it still fails, test the exact same API call via curl with the same key to confirm the key has access to that specific operation/endpoint.
- Check the external service’s dashboard — some APIs (ElevenLabs free tier, fal.ai) disable specific operations based on plan tier, which returns 401 even with a valid key.
# Test ElevenLabs text-to-speech directly
curl -X POST https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM \
-H "xi-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "test", "model_id": "eleven_monolingual_v1"}'
# If this returns 401, the issue is with your key/plan, not n8n
Section B: Wrong Endpoint – /rest/ vs /api/v1/ Confusion
This catches a huge number of developers. n8n has two distinct API route structures and they behave differently with API key authentication:
| Endpoint prefix |
Purpose |
Auth method |
API key works? |
/api/v1/ |
Public REST API (workflows, executions, credentials) |
x-n8n-api-key header |
✅ Yes |
/rest/ |
Internal editor API (used by the n8n UI itself) |
Session cookie / browser auth |
❌ No – returns 401 |
The /rest/ endpoints are what n8n’s own browser UI uses. They require session-based authentication, not API key headers. If you’re hitting /rest/workflows with your API key, you’ll always get 401 regardless of how correct your key is.
Fix – use the correct public API path:
# WRONG — internal endpoint, API key doesn't work here
curl -H "x-n8n-api-key: YOUR_KEY" http://localhost:5678/rest/workflows
# CORRECT — public API endpoint
curl -H "x-n8n-api-key: YOUR_KEY" http://localhost:5678/api/v1/workflows
For self-referencing n8n API credentials (when n8n calls its own API inside a workflow): the Base URL must be your n8n instance’s external URL, not localhost – because n8n inside Docker doesn’t reach itself via localhost. Use the container name or external domain:
# If n8n is in Docker, use the container name or service name from docker-compose
Base URL: http://n8n:5678/api/v1
# NOT: http://localhost:5678/api/v1 ← this resolves inside the container, not to n8n
Section C: JWT Timestamp Bug – Fresh API Key Returns 401 Immediately
This one is genuinely surprising. You generate a brand new API key from the n8n UI, use it immediately — and get 401. You’ve verified the header, the endpoint, everything. The key is completely fresh. It still fails.
n8n API keys are JWT tokens. A confirmed bug (reported October 2025, GitHub issue #21054) causes the JWT’s iat (issued-at) timestamp to be set to a future timestamp — typically 6–7 minutes ahead of the actual current time. JWT validation rejects tokens whose iat is in the future, because it means “this token hasn’t been issued yet.” The result is a 401 that has nothing to do with your key value.
# Decode your API key (it starts with eyJ...) to check the iat field
echo "YOUR_JWT_API_KEY" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
# Look for: "iat": 1761135964
# Convert to human time: date -d @1761135964
# If iat is in the future, you've hit this bug
Fix 1: wait 10 minutes. Once the server clock catches up to the iat timestamp, the key becomes valid automatically. This is the easiest workaround.
Fix 2: sync the container clock. The root cause is clock drift between the Docker container and the host. Fix it:
# Check time inside the n8n container
docker exec -it n8n date
# If it differs from host time, sync the host NTP
sudo timedatectl set-ntp true
# Then restart the container
docker restart n8n
Fix 3: regenerate the key after the clock is synced. The existing key’s iat is baked in — you must generate a new key after fixing the clock drift for the timestamp to be correct.
Section D: N8N_ENCRYPTION_KEY Mismatch: Credentials Silently Break After Update or Restart
This is the highest-impact, least-documented cause of “API key not recognized” in production. After a Docker container rebuild, an n8n version update, or a server migration, every stored credential silently stops working. The error in the execution log looks identical to a wrong API key:
Authorization failed - please check your credentials
# or
Credentials could not be decrypted.
The likely reason is that a different 'encryptionKey' was used to encrypt the data.
What’s happening: n8n encrypts all stored credentials (API keys, passwords, OAuth tokens) using a key defined by N8N_ENCRYPTION_KEY. On first launch, if you don’t set this variable explicitly, n8n generates a random key and saves it to ~/.n8n/config. If that file disappears — because you rebuilt the Docker volume, migrated servers, or forgot to persist the .n8n directory — n8n generates a new random key. It can no longer decrypt the credentials stored with the old key. Every workflow that touches any credential will return 401 or “credentials could not be decrypted.”
How to prevent this? (do this before it happens)
# Generate a stable encryption key and save it NOW
openssl rand -hex 32
# Add to your .env file
N8N_ENCRYPTION_KEY=your_generated_64_char_hex_string
# In docker-compose.yml, set it explicitly
environment:
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
How to recover if it already happened?
- Find the old encryption key. Check
~/.n8n/config on the original host — it’s stored there as "encryptionKey": "...". If you have a backup of the .n8n directory, extract it from there.
- Set that exact key as
N8N_ENCRYPTION_KEY in your environment and restart n8n.
- If the old key is completely lost, there is no way to decrypt the stored credentials. You must recreate all credentials from scratch.
# Find the encryption key from a running container's config
docker exec -it n8n cat /home/node/.n8n/config | grep encryptionKey
# Or from a backed-up .n8n directory
grep encryptionKey /path/to/backup/.n8n/config
EEFA note: In queue mode (multiple n8n workers), every worker and the main instance must load the identical N8N_ENCRYPTION_KEY value. If even one worker has a different key, credentials will silently fail on that worker’s executions but work fine on others — making this extremely hard to diagnose.
Section E: Reading Real n8n Logs to Diagnose API Key Errors
Most developers check the n8n UI execution error and stop there. The real diagnostic information is in the container logs. Here’s how to read them and what to look for:
Enable verbose logging
# Add to your .env or docker-compose environment
N8N_LOG_LEVEL=debug
# Tail live logs
docker logs -f n8n 2>&1 | grep -i "api\|auth\|401\|key\|credential"
What each log pattern means?
| Log line |
What it means |
Fix |
API key not recognized |
Header received but value doesn’t match stored key |
Sections 1–4: verify key value, whitespace, reload env |
Unauthorized with no additional message |
Header is missing entirely — never reached auth middleware |
Section 5: proxy stripping header, or wrong endpoint (Section B) |
Credentials could not be decrypted |
Encryption key mismatch after restart/migration |
Section D: recover or reset N8N_ENCRYPTION_KEY |
Request failed with status code 401 inside a workflow node |
External API rejecting the credential n8n is sending |
Section A: hidden characters, wrong auth method, plan limits |
iat set to future timestamp or token rejected immediately |
JWT clock drift bug |
Section C: sync container clock, wait 10 min, regenerate key |
Section F: Verify the Fix: Confirm Your API Key Is Working
After applying any fix, don’t assume it worked – verify it with a direct test before updating your application code.
Minimal verification curl
# Replace with your actual instance URL and key
curl -s -o /dev/null -w "%{http_code}" \
-H "x-n8n-api-key: YOUR_KEY" \
https://your-n8n-instance.com/api/v1/workflows
# Expected output: 200
# If you see 401: key is wrong or not loaded
# If you see 404: you're hitting the wrong endpoint path
Verbose debug to see exactly what’s being sent
curl -v \
-H "x-n8n-api-key: YOUR_KEY" \
https://your-n8n-instance.com/api/v1/workflows 2>&1 | grep -E "^[><]"
# Look for this line in output:
# > x-n8n-api-key: YOUR_KEY
# If that line is missing, your client isn't sending the header at all
Test through the proxy specifically
# Test DIRECT (bypassing proxy)
curl -H "x-n8n-api-key: YOUR_KEY" http://localhost:5678/api/v1/workflows
# Test VIA PROXY (through your domain)
curl -H "x-n8n-api-key: YOUR_KEY" https://n8n.yourdomain.com/api/v1/workflows
# If direct works but proxy fails → Section 5 (proxy stripping header)
9. Next Steps
- Securely store API keys – explore Vault, AWS Secrets Manager, or Docker secrets.
- Implement request retries with exponential back‑off for transient 401 errors.
- Monitor authentication failures via Grafana or Prometheus alerts.
Conclusion
The “API key not recognized” error is almost always a mismatch between the key stored in n8n and the header sent by the client. By verifying the exact key, ensuring the x-n8n-api-key header is correctly spelled and whitespace‑free, and restarting the service after any environment change, the issue disappears. Remember to forward the header through any reverse proxy and keep keys out of source control. Following the checklist and the proxy‑preservation tips guarantees reliable, production‑grade API access to n8n.