The 2026 app-password removal is probably your 401
If a Bitbucket Cloud credential that used to work now returns 401, the most likely cause in 2026 is that it is a deprecated app password. Atlassian is removing app passwords on a fixed schedule: new ones could no longer be created after 9 September 2025, controlled brownouts began on 9 June 2026, and they are permanently removed on 28 July 2026 — after 9 June 2026, "API tokens are the only way to use Basic authentication" (Using app passwords, 401 while authenticating to Bitbucket Cloud REST APIs). As of this writing (2026-06-26) the brownouts are already running, so an app-password integration will fail intermittently and then permanently. The fix is to migrate to a scoped API token or an access token, not to regenerate the app password.
The other 401 causes are the classic ones: the wrong username format, an Atlassian account password used where a token is required, or a token created without the scopes the request needs.
Why a 401 happens
Bitbucket's troubleshooting KB lists the documented 401 causes (401 while authenticating):
- Wrong username for the credential type — a Bitbucket username pairs with an app password, while an Atlassian account email pairs with an API token. Mixing them fails.
- Using the account password — "The account's password will NOT work" for the API; account passwords were disabled for API and Git use back on 1 March 2022 (Deprecation of password authentication).
- A token with no scopes — "A token without scopes will not work." An API token authenticates but is denied every resource until it has the scope for that resource.
Note Atlassian's Cloud docs fold scope/permission problems under 401 and do not cleanly separate a 401 from a 403 the way some APIs do — so treat 401 as the documented auth-failure code and read the message rather than assuming a strict 401-vs-403 split.
Diagnose it
1. Identify the credential type. Is the integration using an app password (legacy, being removed), an API token, or an access token? If it is an app password in 2026, that is almost certainly the problem — stop and migrate.
2. Check the username/credential pairing. API tokens use Basic auth with the Atlassian account email as the username; app passwords use the Bitbucket username. A 401 right after a credential swap is usually this mismatch.
3. Confirm the scopes. API token scopes are granular and set at creation; you cannot widen them later without re-issuing. The read scopes a read-only integration needs (API token permissions):
read:repository:bitbucket— "Allows viewing of repository data" (note this scope does not grant access to a repository's pull requests).read:pullrequest:bitbucket— "Allows viewing of pull requests, plus the ability to comment on pull requests."read:pipeline:bitbucket— "Allows read access to all pipeline information (pipelines, steps, caches, artifacts, logs, tests, and code-insights)."read:runner:bitbucket— runner status, if you read it.
A token missing read:pipeline:bitbucket will read repos and PRs but fail on pipeline endpoints — which looks like a partial outage, not an auth problem, until you check the scope.
A scope-vocabulary trap
There are two different scope vocabularies and they are easy to conflate. API tokens use the granular read:*:bitbucket strings above. Classic OAuth / Connect apps use coarse scopes — repository, pullrequest (which implies repository), account — and that classic model does not expose a pipeline scope (Bitbucket Cloud REST API scopes). So if you are issuing an API token, ask for read:repository:bitbucket / read:pullrequest:bitbucket / read:pipeline:bitbucket; if you are building an OAuth app, you request repository / pullrequest. Requesting the wrong vocabulary for your auth method produces a token that does not grant what you expected.
Resolution options
Migrate off app passwords (do this now). Replace each app-password integration with either:
- An API token with scopes — the direct replacement for app-password Basic auth, scoped to least privilege, optionally workspace-bound. It cannot be viewed after creation, so store it on issue (API tokens).
- A workspace / project / repository access token — not tied to a person, ideal for a service integration, and these also qualify for the scaled rate limit (below).
Fix a scope gap. Re-issue the token with the missing read scope; you cannot add a scope to an existing token. Grant only the read scopes you need.
Fix a pairing error. Use the account email with an API token, or the Bitbucket username with an app password — matched to the credential type.
Rate limits (the 429 case)
If requests start returning 429, you hit the Bitbucket Cloud limit. Authenticated requests are capped at 1,000 per hour, measured per user ID over a rolling one-hour window; anonymous is 60 per hour per IP (API request limits). Standard/Premium workspaces with 100+ paid users using access tokens (or Forge asApp) get +10 requests/hour per paid user beyond 100, capped at 10,000/hour (API request limits). The response carries X-RateLimit-Limit, X-RateLimit-Resource, and X-RateLimit-NearLimit (true under 20% remaining); a 429 means back off (Rate limit troubleshooting).
Recommended approach (with tradeoffs)
For a service that continuously reads Bitbucket, use a workspace or repository access token rather than a personal API token. It is decoupled from any individual's account (so it survives offboarding), scoped to least privilege, and — crucially for throughput — it is the credential type that qualifies for the scaled rate limit on larger paid workspaces, where a personal token stays pinned at 1,000/hour. The tradeoff is setup: access tokens are managed at the workspace/repo level and require admin to issue and rotate, which is more process than a personal token. For a one-off script, a scoped API token is fine; for a platform, the access token's durability and higher ceiling are worth the extra step.
Whatever you choose, do not build new integrations on app passwords — they are being removed within weeks of this writing, and anything depending on them will break.
Validation steps
# Read a PR with the migrated credential (Basic auth: account email + API token).
curl -s -u "$ATLASSIAN_EMAIL:$API_TOKEN" \
"https://api.bitbucket.org/2.0/repositories/{workspace}/{repo}/pullrequests/{id}" \
-o /dev/null -w '%{http_code}\n'
# want: 200 (401 = credential/scope problem; 429 = rate limited)
# Read a pipeline — proves the read:pipeline scope is present.
curl -s -u "$ATLASSIAN_EMAIL:$API_TOKEN" \
"https://api.bitbucket.org/2.0/repositories/{workspace}/{repo}/pipelines/{uuid}" \
-o /dev/null -w '%{http_code}\n'
A 200 on both confirms the credential authenticates and carries the repository and pipeline read scopes. A 401 on the second but 200 on the first is the classic missing-read:pipeline:bitbucket signature.
Prevention strategies
- Inventory and migrate app passwords before the 28 July 2026 removal — anything still on one will hard-fail, and the brownouts mean it is already failing intermittently.
- Scope tokens to least privilege (read-only repository, pull request, pipeline) so a leaked credential cannot write.
- Prefer access tokens for services so credentials survive personnel changes and qualify for the higher rate limit on paid workspaces.
- Watch
X-RateLimit-NearLimitand slow down before the 429, rather than reacting to failed requests after the fact.
Operational lessons
The dangerous property of this particular failure is its timing: app-password removal is a scheduled break, so an integration that has worked for years will fail on a date, not because anything in your code changed. Treating the deprecation dates as a hard deadline — inventory now, migrate to scoped tokens — turns a future outage into a routine credential swap.
The scope split is the other quiet trap: a token that reads repos and PRs but silently 401s on pipelines looks like a flaky service, not an auth gap, and sends people debugging the wrong layer. For a platform that reads commits, PRs, and pipeline results together to reconstruct what changed, an under-scoped token produces an incomplete picture — the PR is there but the build that gates it is invisible. Granting all three read scopes (repository, pull request, pipeline) on a durable access token keeps that read path whole.