Bitbucket Cloud retired app passwords in favour of scoped API tokens. If a Jenkins multibranch pipeline authenticates to Bitbucket with an app password, it doesn’t fail loudly the day the deprecation lands - it fails quietly, and only in specific spots, which makes it confusing to diagnose. This is the exact failure, why it shows up where it does, and the fix.
Problem
A multibranch pipeline job that had been running fine for months suddenly failed on a manual “Build Now”:
com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException: HTTP request error.
Status: Unauthorized HTTP 401
Response: {"type": "error", "error": {"message": "App passwords are deprecated and must be replaced with API tokens." ...}}
at ...BitbucketCloudApiClient.getBranch(BitbucketCloudApiClient.java:381)
at ...BitbucketSCMSource.retrieve(BitbucketSCMSource.java:639)
at ...SCMSource.fetch(SCMSource.java:577)
at ...SCMBinder.create(SCMBinder.java:104)
at ...WorkflowRun.run(WorkflowRun.java:320)
The confusing part: webhook-triggered builds on the same job kept working. Only manual triggers failed.
Root cause
Why manual triggers fail but webhook builds don’t
A push webhook from Bitbucket carries the new commit SHA in its payload. Jenkins’ branch indexing consumes that and queues the build with the revision already resolved - the build never needs to call back to Bitbucket’s API to ask “what’s HEAD right now.”
A manual “Build Now” has no pre-resolved revision attached. So SCMBinder.create() - the lightweight “Pipeline script from SCM” step that fetches just the Jenkinsfile before running anything - has to call SCMSource.fetch() → BitbucketSCMSource.retrieve() → getBranch() to resolve the branch head itself. That live API call is what hits the dead credential.
This matters beyond manual builds: branch indexing scans (the periodic “Scan Multibranch Pipeline” that discovers new branches) call the same API with the same credential. If the credential is broken, those are failing too - just less visibly, since a failed scan doesn’t show up as a red build.
Two separate root causes, not one
Fixing this took two passes, because there are two independent things app passwords used to paper over:
- Basic Auth identity - Bitbucket’s REST API requires the Atlassian account email as the username when authenticating with a scoped API token. The Bitbucket handle (which worked fine with app passwords, and still works for plain
git clone/git fetchover HTTPS) does not work for REST API calls. - Token scopes - a scoped token only grants what you explicitly select. The legacy app password’s effective permissions (
webhook,pullrequest,team) need to be mapped to the new scope model - missing scopes don’t always 403; some endpoints come back as a bare 401 with no explanation.
Steps to fix
1. Create a scoped API token
In Bitbucket: Personal settings → API tokens → Create API token with scopes. Select Bitbucket as the app, set an expiry, and grant:
read:repository:bitbucket,write:repository:bitbucket- git operations, branch listing, commit/build statusread:pullrequest:bitbucket,write:pullrequest:bitbucket- PR discovery and statusread:webhook:bitbucket,write:webhook:bitbucket- if Jenkins auto-manages the webhookread:account,read:workspace:bitbucket- needed to enumerate repos under a workspace/owner; this is the closest equivalent to the legacyteamscope, and the one most likely to be missing if you only mappedrepository+pullrequest
Copy the token immediately - it’s shown once.
2. Validate with curl before touching Jenkins
This step saved several rounds of trial-and-error. Test the exact calls Jenkins makes, with the Atlassian email as username:
# Owner/workspace repo listing - powers the "Repository Name" dropdown in the job config
curl -u "[email protected]:ATATT...<token>" "https://api.bitbucket.org/2.0/repositories/<workspace>" -v
# Branch listing for the specific repo
curl -u "[email protected]:ATATT...<token>" "https://api.bitbucket.org/2.0/repositories/<workspace>/<repo>/refs/branches" -v
# Pull request listing
curl -u "[email protected]:ATATT...<token>" "https://api.bitbucket.org/2.0/repositories/<workspace>/<repo>/pullrequests" -v
A few gotchas hit along the way:
- Using the Bitbucket username instead of the email gives a
401with no useful body - easy to mistake for a token problem when it’s actually a username problem. - New tokens can take up to a minute to start working. A
401immediately after creation isn’t necessarily wrong scopes - wait and retry before changing anything. - Don’t paste a live token into a chat log, ticket, or shared doc to “show someone the error” - if it happens, revoke and regenerate immediately rather than leaving it live.
Only move to Jenkins once all three calls return 200.
3. Update the Jenkins credential
Manage Jenkins → Credentials → find the credential bound to the job’s Branch Source (check Configure → Branch Sources → Credentials on the job to identify which one) → Update:
- Username: the verified email
- Password: the new token
Keep the same credential ID if it’s shared across multiple jobs - every job referencing it is fixed in one place, no per-job edits needed.
4. Re-verify
- Reopen the job’s Branch Source config - the Repository Name field (an owner→repo lookup dropdown) should populate without a validation error. If it stays empty, the workspace-read scope is still missing.
- Run Scan Multibranch Pipeline Now - confirms branch/PR/tag discovery works end to end.
- Manually trigger a build - confirms the original
401is gone.
Notes
- Don’t “fix” this by switching the Branch Source type from Bitbucket to plain Git. Plain git over HTTPS only needs the token to authenticate - it doesn’t touch the REST API, so it’ll work even with a partially-broken/under-scoped token. But you lose native PR and tag discovery: PR-derived branches just get dumped into the flat branch list with no PR awareness. It looks like a fix and isn’t one - revert to the Bitbucket source type once the actual credential is corrected.
- If the Branch Source’s tool config shows an “Edited” marker or was recently changed, check it - an unrelated config edit can coincide with the deprecation cutover and make the timeline confusing.
- A shared credential across many multibranch jobs is good news once you find it - one fix in Credentials propagates everywhere without touching individual job configs.
- Audit Manage Jenkins → Credentials for any other
apppassword-type entries once you’re done - Bitbucket’s cutover affects every job using one, not just the one that happened to fail first.