如何修復 LLM API 的 401 Unauthorized 錯誤
How to Fix 401 Unauthorized Errors in an LLM API
文章整理排查 LLM API 401 Unauthorized 錯誤的步驟,指出 401 表示伺服器拒絕目前提交的憑證,不一定是 API key 本身錯誤。檢查 API key 是否已傳送、含有隱藏空白或已失效,並確認 base URL 與發出 key 的服務相符;先以原始 HTTP 請求測試,可分辨 key 問題與應用程式設定問題。
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'...}}
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()")
With raw HTTP, the header must include the scheme, exactly once:
Authorization: Bearer sk-...
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())
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"}]}'
-
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] + "...")
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.")
Retry budgets belong to 429 and 5xx errors — that's the next post in this series.
Common Mistakes
- Debugging 403 problems as if they were 401 (see the table above — account state vs credentials).
- Hardcoding the key to "test quickly," then the hardcoded key rotates and prod breaks mysteriously.
- Sharing a screenshot of your terminal that contains the full key. Rotate immediately if this happens.
- Mixing two providers' keys and base URLs in the same
.envfile.
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.
來源:DEV Community · dev.to