Implementing n4n.ai bearer token api authentication in your client is mostly about respecting the RFC 6750 contract, but the operational details decide whether your calls survive a provider outage or a key leak. The gateway rejects any request missing a valid Authorization: Bearer <token> header with a 401 before it reaches a model, so getting this right is the first gate to the 240+ models behind the endpoint.
Step 1: Provision a token from the dashboard
Generate an API key in the project console and copy it exactly once. The token is an opaque signed string; the gateway does not display it again. Treat it like a database password, not like a user identifier.
Store it in your environment or a secret manager, never in source control:
export N4N_API_KEY="sk-live-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
If you run multiple environments, issue separate tokens per environment. Scoping is not enforced by the token string itself but by the project boundary it belongs to, so isolation comes from how you distribute the secret, not from any prefix logic you invent.
Step 2: Send the Bearer header on every request
The auth check is stateless. Every call must carry the header, including retries that you initiate after a network error (but not after a 401). The header format is strict: one space between Bearer and the token, no trailing whitespace, no Basic or Token scheme.
Raw curl:
curl https://api.n4n.ai/v1/chat/completions \
-H "Authorization: Bearer $N4N_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"ping"}]}'
Python with requests:
import os
import requests
resp = requests.post(
"https://api.n4n.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['N4N_API_KEY']}"},
json={"model": "gpt-4o", "messages": [{"role": "user", "content": "ping"}]},
)
TypeScript with fetch:
const res = await fetch("https://api.n4n.ai/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.N4N_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "ping" }],
}),
});
A common bug is reading the key from a file with a trailing newline. Strip it before placing it in the header or you will get a 401 with invalid_token and waste an hour.
Step 3: Target the OpenAI-compatible endpoint
The n4n.ai bearer token api authentication scheme guards a single OpenAI-compatible endpoint that fronts 240+ models, so you can point existing OpenAI SDKs at it by changing the base URL. The SDK already knows how to emit the Bearer header from the api_key field, so you do not hand-roll headers in that path.
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["N4N_API_KEY"],
base_url="https://api.n4n.ai/v1",
)
chat = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "Explain bearer auth"}],
)
Because the surface is OpenAI-compatible, the same token works across every model name you pass in model, from OpenAI to Mistral to local weights. The gateway resolves the routing after auth, not before.
Step 4: Handle authentication failures
A 401 means the token was absent, malformed, or revoked. A 403 means the token is valid but the project lacks access to the requested route or model. A 429 is rate limiting and is unrelated to auth—retrying with backoff is correct there.
Parse the error body instead of guessing:
if resp.status_code == 401:
# do not retry with same token; surface to operator
raise RuntimeError(f"Auth failed: {resp.json().get('error', {}).get('message')}")
elif resp.status_code == 403:
# token ok, permission denied
raise PermissionError(resp.json().get("error", {}).get("message"))
elif resp.status_code == 429:
# retry with backoff, token is fine
pass
Never log the full Authorization header. If you log requests for debugging, redact the value at the middleware layer. The gateway will not echo your token back, but your own logs will if you are careless.
Step 5: Rotate tokens without downtime
Static long-lived keys eventually leak. Build a rotation path before you need it. Issue a second token, deploy it to your secret store alongside the first, and confirm traffic works with both. Then revoke the old token.
# issue new, keep old in env as N4N_API_KEY_2
export N4N_API_KEY_2="sk-live-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
In code, try the primary, fall back to secondary on 401 only during a rotation window:
def post_with_rotation(url, payload):
for key in [os.environ["N4N_API_KEY"], os.environ.get("N4N_API_KEY_2")]:
if not key:
continue
r = requests.post(url, headers={"Authorization": f"Bearer {key}"}, json=payload)
if r.status_code != 401:
return r
raise RuntimeError("All keys rejected")
After the old key is revoked and metrics show zero 401s on it, remove the fallback. This avoids a hard cutover and a 3 a.m. page.
Step 6: Combine auth with routing and cache hints
Beyond the bearer token auth layer, the gateway also meters per-token usage and automatically falls back when a provider is degraded, so your auth layer stays out of the retry path. You can pass provider-specific cache-control inside the messages payload; the gateway forwards those hints while your bearer token authenticates the call. For example, Anthropic-style ephemeral caching works through the compatible endpoint:
resp = requests.post(
"https://api.n4n.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['N4N_API_KEY']}"},
json={
"model": "anthropic/claude-3.5-sonnet",
"messages": [
{"role": "system", "content": "You are helpdesk.", "cache_control": {"type": "ephemeral"}},
{"role": "user", "content": "What is my balance?"},
],
},
)
The token authenticates; the cache_control field is forwarded untouched. Keep the two concerns separate in your code: auth is a header, model behavior is a body field.
Step 7: Verify success
A successful authenticated call returns HTTP 200 and a JSON body containing a usage object. The presence of usage confirms the gateway accepted your token, routed the request, and metered the response.
assert resp.status_code == 200
data = resp.json()
assert "usage" in data, "missing usage means non-standard response"
print(data["usage"])
# example: {'prompt_tokens': 12, 'completion_tokens': 8, 'total_tokens': 20}
If you are using the OpenAI SDK, the same field is chat.usage. Write a smoke test in CI that calls a cheap model like gpt-4o-mini and asserts on usage.total_tokens > 0. That test catches expired tokens, wrong environment, and base-URL typos in one shot.
Step 8: Production checklist
- Store tokens in a secret manager; inject at runtime.
- Strip whitespace when reading keys from files or env.
- Send
Authorization: Bearer <token>on every request, including manual retries. - Distinguish 401 (bad token) from 403 (bad scope) from 429 (rate limit).
- Rotate with a dual-key window; never hard-cut.
- Redact
Authorizationin logs and proxies. - Assert on
usagein a CI smoke test against a low-cost model.
Following these steps gives you a client that authenticates cleanly, degrades safely when a provider is unhealthy, and keeps your keys out of the incident report.