DEV Community

Maggie fu
Maggie fu

Posted on

How to Fix 401 Unauthorized Errors in an LLM API

The Problem

401 Unauthorized is one of the most common first-day errors in any LLM API project. It's also the one that eats the most time, because the body says the same thing whether your key is wrong or just badly copied:

openai.AuthenticationError: Error code: 401 - {'error': {...'invalid api key'...}}
Enter fullscreen mode Exit fullscreen mode

The confusing part: 401 doesn't mean your key is wrong. It means the server refused to accept your credentials as presented. That gap covers several very different causes. Here's the checklist I run through, in order of likelihood.

Quick Definitions: 401 vs 403 vs 429

Code Meaning Usual cause
401 Server didn't accept your credentials Key missing, malformed, or invalid
403 Credentials accepted, but you're not allowed Account state, permissions, regional restrictions
429 Too many requests Rate limits — retry with backoff, see my next post

If you're getting 403, this isn't your article — but the table above saves you an hour of debugging the wrong thing.

The Checklist

1. Is the key actually being sent?

With an SDK, an empty or misnamed environment variable silently produces "no key":

import os
key = os.environ.get("HEFUAI_API_KEY")
if not key:
    raise SystemExit("HEFUAI_API_KEY is not set — check .env and load_dotenv()")
Enter fullscreen mode Exit fullscreen mode

With raw HTTP, the header must include the scheme, exactly once:

Authorization: Bearer sk-...
Enter fullscreen mode Exit fullscreen mode

Authorization: sk-... (no Bearer) and Bearer: sk-... (schemes swapped) both produce 401 on many gateways.

2. Is there invisible whitespace in the key?

The cause that wastes the most time. Copying a key out of a dashboard or a chat message drags along a trailing newline or space:

key = os.environ["HEFUAI_API_KEY"]
print("leading/trailing whitespace:", key != key.strip())
Enter fullscreen mode Exit fullscreen mode

Fix it at the source (your .env file), not with .strip() everywhere — but if the key comes from user input or a config system you don't control, strip it defensively.

3. Test the key independently of your app

Before you blame your framework, send one raw request. This isolates key validity from code:

curl -s -o /dev/null -w "%{http_code}\n" \
  https://api.hefu.hk/v1/chat/completions \
  -H "Authorization: Bearer $HEFUAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "deepseek-v4-flash", "messages": [{"role": "user", "content": "ping"}]}'
Enter fullscreen mode Exit fullscreen mode
  • 200 → your key is fine; the bug is in your app (wrong env var name, dotenv not loaded, per-directory .env)
  • 401 → the key itself is the problem; continue to #4

4. Was the key rotated, revoked, or never saved correctly?

Keys get invalidated more often than people admit:

  • Someone on the team rotated the key "for security" and your deploy didn't get the memo
  • You regenerated the key while debugging and are now running two versions
  • The key was never fully copied in the first place

Worth knowing how your provider stores keys. For example, HefuAI shows the plaintext once at creation and never again — so if you didn't save it, regeneration is the only path, not "view it again." If a key might be compromised, revoke and rotate in the console rather than hoping it blows over.

5. Are you hitting the right base URL with the right key?

A subtle one: if your base_url points at a different gateway or vendor than the one that issued your key, you'll get a 401 that has nothing to do with the key's validity. Same class of bug: dev keys against prod endpoints. Print both, every time, during setup:

print("base_url:", client.base_url)
print("key prefix:", os.environ["HEFUAI_API_KEY"][:6] + "...")
Enter fullscreen mode Exit fullscreen mode

Don't Retry a 401

A rookie mistake is wrapping everything in a retry loop. 401 is not transient — retrying the same credentials will fail the same way 100% of the time, and retrying can even trigger rate-limit lockouts. Handle it explicitly and fail fast:

from openai import OpenAI, AuthenticationError

try:
    resp = client.chat.completions.create(
        model="deepseek-v4-flash",
        messages=[{"role": "user", "content": "ping"}],
    )
except AuthenticationError:
    # stop the pipeline, alert a human, check the checklist above
    raise SystemExit("Auth failed — do NOT retry. Check key, header, base URL.")
Enter fullscreen mode Exit fullscreen mode

Retry budgets belong to 429 and 5xx errors — that's the next post in this series.

Common Mistakes

  1. Debugging 403 problems as if they were 401 (see the table above — account state vs credentials).
  2. Hardcoding the key to "test quickly," then the hardcoded key rotates and prod breaks mysteriously.
  3. Sharing a screenshot of your terminal that contains the full key. Rotate immediately if this happens.
  4. Mixing two providers' keys and base URLs in the same .env file.

Next Steps

Once auth works, the next error you'll meet in production is 429 Too Many Requests. The next post covers rate limits, exponential backoff, and building retry logic that actually respects Retry-After headers.


Disclosure: this tutorial series uses HefuAI as its example gateway. All code patterns apply to any OpenAI-compatible endpoint.

Top comments (2)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.