How to Fix a 401 Unauthorized Error
Read what the site's challenge asks for, then fix the token, the redirect or the header, with copy-ready code.
Updated
TL;DR
401 Unauthorized means the website or API you called, not your proxy, refused the request because it carried no valid login or token. The fastest fix is to get a fresh token and send it in the scheme the response's WWW-Authenticate header names.
What a 401 looks like in each client
Most clients print only the status; the WWW-Authenticate header says what the site wants.
| Where | What you see |
|---|---|
| Chrome, empty 401 body, below the heading This page isn't working | If the problem continues, contact the site owner. HTTP ERROR 401 |
| curl with --fail | curl: (22) The requested URL returned error: 401 |
| Python urllib | urllib.error.HTTPError: HTTP Error 401: Unauthorized |
| Python requests, raise_for_status() | requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://api.example.com/v1/items |
| axios (Node.js) | AxiosError: Request failed with status code 401 |
| API response header, expired OAuth token | WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired" |
Why this happens
The site wants proof of who you are, and this request lacked a valid one.
Servers compare the Authorization header with the logins and tokens they issued. RFC 9110 makes them name the accepted scheme, such as Basic, Bearer or Digest, in a WWW-Authenticate header on every 401.
Diagnose your 401 first
Each check points to one cause, and the first needs only a browser.
Open the URL in Chrome; if a username and password dialog appears, the site wants an HTTP login such as Basic, not a token.
On a Bearer API, print WWW-Authenticate; error="invalid_token" means the token arrived but is expired, revoked or malformed, while a bare challenge usually means none arrived.
Run curl -v -L with your token; if a Location header moves you to another host before the 401, the token was dropped there.
Through a proxy, run curl -v; a 401 after CONNECT's 200 reply came from the site, while a CONNECT error reply came from the proxy.
Print the headers your code sends; if Authorization decodes to your proxy username, the site received the proxy login instead of its token.
Solutions ranked by effectiveness
Start with the token itself, then the route it travels.
- Most common fix
Send the credential the challenge names
Applies when the header is absent, mistyped or carries a stale value. Copy the format the API documents; for Bearer that is the word Bearer, one space, then the token, with no quotes.
import urllib.error import urllib.request req = urllib.request.Request( "https://api.example.com/v1/items", headers={"Authorization": "Bearer TOKEN"}, ) try: with urllib.request.urlopen(req, timeout=30) as r: print(r.status, r.read().decode()) except urllib.error.HTTPError as e: print(e.code, e.headers.get("WWW-Authenticate")) - Check next
Call the final address directly
Applies when the first answer is a redirect. curl and Node.js fetch drop Authorization when the scheme, host or port changes; Python requests does too, except http to https on ports 80 and 443.
Replace http:// with https:// in the URL your code calls.
Match the final hostname exactly, with or without www, as the redirect shows.
Use curl --location-trusted only when you trust every host in the chain.
- For developers
Keep the proxy login out of Authorization
Applies when you call the site through a proxy. Give the proxy its login in the proxy settings, sent as Proxy-Authorization; whatever sits in Authorization travels on to the site.
import urllib.error import urllib.request proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000" opener = urllib.request.build_opener( urllib.request.ProxyHandler({"http": proxy, "https": proxy})) req = urllib.request.Request("https://api.example.com/v1/items", headers={"Authorization": "Bearer TOKEN"}) try: print(opener.open(req, timeout=30).status) except urllib.error.HTTPError as e: print(e.code, e.headers.get("WWW-Authenticate"))
Stop the 401 from coming back
Habits for scripts and integrations that call authenticated APIs every day.
Turn each token's expires_in into a clock time on arrival, and refresh shortly before then.
Retry a 401 once with new credentials; if the same challenge returns, stop and read the response body.
Write the WWW-Authenticate value into your logs beside each 401, so the cause is on record without a rerun.
Sign in to your own accounts through a sticky port, since some sites end a session when its IP changes.
Related errors
Learn more
FAQ
A separate login for the proxy
Geonode's dashboard gives the proxy its own username and password, apart from every site login.


