The GitHub API returns 401 with the message Requires authentication when the request reached an endpoint that needs a token and no valid token arrived. Five things cause that, and they are worth checking in order: the Authorization header is missing entirely, the header is in the wrong form, the token has expired or been revoked, the token is fine-grained and does not have permission for that repository or resource, or the endpoint recently stopped being public, which is what happened to secret gists. The message is the same for all five, so the fix depends on reading your request, not the response.
The top answers for this error each solve one cause and leave the reader who has a different cause stuck. Most of the recent reports are the fourth and fifth causes, fine-grained token scopes and endpoints that quietly moved behind authentication, and those are the ones the old answers do not mention. Here is the full list, a way to test each, and the header forms that actually work.
Table of contents
- What the 401 is telling you
- Cause 1: no token was sent
- Cause 2: the header is in the wrong form
- Cause 3: the token expired or was revoked
- Cause 4: a fine-grained token without the scope
- Cause 5: the endpoint stopped being public
- Tokens in a deploy pipeline
- How this fits the rest of the stack
- FAQ
What the 401 is telling you
GitHub uses two different codes for two different problems, and it helps to keep them apart.
- 401 Requires authentication. GitHub did not receive credentials it could use. Either nothing was sent, or what was sent was not recognised as a token at all.
- 401 Bad credentials. A token was recognised and rejected: expired, revoked, mistyped, or belonging to a deleted account.
- 403 Resource not accessible. The token was accepted but does not have permission for this resource. Fine-grained tokens without the right repository access land here, as do rate limits.
- 404 Not Found. On private resources GitHub returns 404 rather than 403 to avoid confirming the resource exists. A private repository queried with a token that cannot see it looks like it does not exist.
So Requires authentication specifically means: send a token, or send it properly. If you are sure you sent one, the problem is the form of the header, which is the second cause below.
Cause 1: no token was sent
Most common by a distance. The request was written against an endpoint that used to work anonymously, or the token variable was empty because the environment variable was not set in the place the code ran.
Anonymous requests to the REST API still work for public data, but at sixty requests an hour and with a growing list of exceptions. Anything under /user, anything on a private repository, creating or modifying anything, and since a 2025 policy change, reading a secret gist, all require a token.
The quickest check is to print the header you are about to send. An empty Authorization value, or a value of Bearer followed by nothing, is this cause. In CI, the usual culprit is a secret that is set on the repository but not exposed to the job, or a fork that cannot see secrets at all.
Cause 2: the header is in the wrong form
GitHub accepts two forms of the Authorization header, and both are fine. What is not fine is the third form people improvise.
# both of these work
curl -H "Authorization: Bearer ghp_xxxxxxxx" https://api.github.com/user
curl -H "Authorization: token ghp_xxxxxxxx" https://api.github.com/user
# this does not: token in the URL
curl https://api.github.com/user?access_token=ghp_xxxxxxxx
Query-string tokens were removed in 2021. Basic authentication with a username and password was removed in 2020; username plus token as Basic auth still works but is the form most likely to be typed wrong. Use Bearer.
Two quieter versions of this cause: a trailing newline on the token, which happens when it is read from a file with cat, and a token pasted with the ghp_ prefix trimmed off because someone assumed it was decoration. It is part of the token.
Cause 3: the token expired or was revoked
Classic personal access tokens can be created with no expiry; fine-grained tokens must expire, with a maximum of a year. A token that worked last quarter and stopped this week almost certainly expired. GitHub emails the account a week before, to an inbox nobody reads.
Tokens are also revoked automatically when GitHub’s secret scanning finds them in a public commit. If a token stopped working at the same moment a push went out, check the security log; the token was leaked and GitHub killed it for you, which is the right outcome.
Confirm with a direct call. A 401 Bad credentials here means the token itself is dead and needs replacing; there is no way to extend it.
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user
Cause 4: a fine-grained token without the scope
Fine-grained tokens are the right default, and they are also the newest source of confusion, because permission is granted per repository and per resource rather than in broad scopes. A token created for one repository returns 404 or 403 on another. A token with Contents read access cannot list issues. A token created under a personal account cannot see an organisation’s repositories until the organisation approves it.
The check is the token’s settings page: which repositories, which permissions. The usual fix is to add the repository or the permission and regenerate. If the request is on behalf of an organisation, the organisation’s settings decide whether fine-grained tokens are allowed at all.
The tell that you are here rather than at cause 1: the same token works on a different endpoint or a different repository.
Cause 5: the endpoint stopped being public
This is the one behind the recent wave of reports. Secret gists used to be readable by anyone who had the URL, and tools fetched them anonymously. GitHub changed that so that secret gists require authentication, and every script that relied on the old behaviour started returning Requires authentication overnight.
The pattern repeats: an endpoint that was public becomes token-only, usually to stop scraping, and the only notice is the changelog. If a request that never sent a token starts failing with this message and nothing in your code changed, look for a GitHub changelog entry before looking for a bug.
The fix is to send a token, which you should have been doing anyway for the rate limit: five thousand requests an hour authenticated against sixty anonymous.
Tokens in a deploy pipeline
Most of these 401s happen in automation rather than at a terminal: a deploy script that clones a private repository, a build step that fetches a release asset, a webhook handler that comments on a pull request. Three habits stop them recurring.
- One token per job, stored as an environment variable on the service that runs the job. Not in the repository, not in a Dockerfile, not in a shell history.
- Fine-grained, scoped to the one repository and the one permission the job needs. When the token leaks, the damage is bounded.
- A calendar entry for the expiry. Or a health check that calls /user once a day and alerts on anything but 200.
On RunxBuild, a service deploys from a GitHub repository through the GitHub App connection, so the deploy itself needs no personal token. Anything the service does with the API at runtime reads its token from the environment variables set on the service, which is where a secret should live: see deploying from GitHub on RunxBuild for the connection, and set the runtime token in the service’s environment rather than in code.
How this fits the rest of the stack
Requires authentication is one message for five causes, and the order above is the order of likelihood: check the header exists, check its form, check the token is alive, check its scope, then check whether the endpoint changed under you. If the token lives in a deploy pipeline, the RunxBuild hosting calculator is a reasonable place to see what the service that runs it costs alongside the database and storage it talks to, and the dashboard is where the environment variable holding the token gets set.
Useful related references:
- GitLab Personal Access Tokens Will Expire: What to Do Before They Do
- How to Add an API Key in Netlify Without Publishing It to the World
- 400 error code in rest api: The Nine Reasons and the Three That Hide Behind “Bad Request”
- Deploying from GitHub on RunxBuild
FAQ
What does GitHub API Requires authentication mean?
The endpoint needs a token and the request did not carry one GitHub could recognise. Send an Authorization header in the form Bearer followed by the token. If you did, the header form, an expired token, a fine-grained token without the right scope, or an endpoint that recently became token-only are the remaining causes.
How do I authenticate to the GitHub API with curl?
Add the header: curl -H Authorization: Bearer YOUR_TOKEN https://api.github.com/user. The older form, token YOUR_TOKEN, also works. Tokens in the query string and username-password Basic auth no longer do.
Why does my GitHub token work on one repo and not another?
It is a fine-grained token scoped to specific repositories, or the second repository belongs to an organisation that has not approved the token. Open the token’s settings and add the repository, or create a token under the organisation’s policy.
Why do I get a 404 instead of a 401 on a private repository?
GitHub returns 404 for private resources the token cannot see, to avoid confirming they exist. Treat a 404 on a repository you know exists as a permissions problem, not a wrong URL.
Why did fetching a secret gist start returning 401?
GitHub changed secret gists to require authentication. They were never private, only unlisted, and anonymous access was removed. Send a token with the request and the same URL works.