Skip to content

Scripting & Automation - Bitbucket CLI for CI/CD Workflows

Add --json for machine-readable output:

Terminal window
bb pr list --json
bb repo list --json
bb pr view 42 --json

Paginated list commands return 25 rows by default. Pass --all to fetch every page, or --limit <n> for a specific cap — --all wins if you pass both. --limit must be a positive integer; anything else fails with --limit must be a positive integer (error code 5002). There is no --page flag; pagination is followed internally.

Terminal window
bb pr list --json --all
bb pr list --json --limit 100

Eleven commands take --limit and --all: repo list, pr list, pr activity, pr comments list, snippet list, snippet comments list, pipeline list, commit list, status list, workspace list, project list. Other list-style commands return their full result set and accept neither flag — bb pr reviewers list and bb repo default-reviewers list, for example.

Two flags inspired by the gh CLI slice and filter output without an external jq binary:

Terminal window
# Project to a comma-separated field list
bb pr list --json id,title,state
# Filter through the embedded jq (requires --json)
bb pr list --json --jq '.pullRequests[].title'
# Combine both
bb pr list --json id,title,state \
--jq '.[] | select(.state == "OPEN") | .title'

When --json fields is passed against a list-style command, the wrapper envelope is dropped and the field list is projected per-item — the result is a flat array. See JSON Output reference for details.

--jq matches the syntax of the standalone jq binary, so existing expressions work either way. These two are equivalent:

Terminal window
bb pr list --json --jq '.pullRequests[].title'
bb pr list --json | jq '.pullRequests[].title'
Terminal window
# Open PR count (--state defaults to OPEN)
bb pr list --json --all --jq '.count'
# Merged PR count
bb pr list -s MERGED --json --all --jq '.count'
# Project specific fields
bb pr list --json --all --jq '.pullRequests[] | {id, title, author: .author.display_name}'
# Filter by author
bb pr list --json --all --jq '.pullRequests[] | select((.author.nickname // .author.display_name) == "alice")'
# PR web URLs
bb pr list --json --all --jq '.pullRequests[].links.html.href'
# Web diff URL for one PR
bb pr diff 42 --web --json --jq '.url'
# PRs updated in the last 7 days
bb pr list --json --all | jq --arg date "$(date -d '7 days ago' -Iseconds 2>/dev/null || \
date -v-7d -Iseconds)" \
'.pullRequests[] | select(.updated_on > $date)'
# PRs targeting main
bb pr list --json --all | jq '.pullRequests[] | select(.destination.branch.name == "main")'
# Group PRs by author
bb pr list --json --all | jq '.pullRequests | group_by(.author.nickname // .author.display_name) | map({author: (.[0].author.nickname // .[0].author.display_name), count: length})'

When no typed command covers what you need, bb api calls any Bitbucket Cloud 2.0 endpoint through the same authenticated stack. Its output is already JSON, so --jq works without --json.

Terminal window
# Any endpoint, authenticated
bb api /user --jq '.username'
# Fields become a query string on GET, a JSON body otherwise
bb api /repositories/myworkspace/myrepo/pullrequests -X GET -f state=MERGED
# Follow pagination and pull a single field across every page
bb api /repositories/myworkspace/myrepo/pullrequests --paginate \
--jq '.values[].title'
# Create resources that have no typed command yet
bb api POST /repositories/{workspace}/{repo}/branch-restrictions --input body.json

{workspace} and {repo} placeholders resolve from -w/-r or the current checkout. See the API command reference for every flag.


Code Meaning
0 Success
1 Any failure (authentication, API, validation, network, jq, …)

There is no 2/3/etc. mapping — every failure exits with 1. To branch on the specific failure mode, read the code field of the JSON error envelope, which is one of the error codes.


In --json mode, success JSON goes to stdout and the error envelope goes to stderr. Redirect them separately:

#!/bin/bash
set -euo pipefail
if ! bb pr view 999 -w myworkspace -r myrepo --json > out.json 2> err.json; then
code=$(jq -r '.code' err.json)
case "$code" in
1001|1002|1003) echo "auth problem";;
2002) echo "PR not found";;
*) echo "other failure ($code): $(jq -r '.message' err.json)";;
esac
exit 1
fi

The error envelope carries:

Field Notes
name Error class — BBError, AuthError, APIError, …
code Numeric error code
message Human-readable failure description
context Extra detail (ids, keys, args); omitted when the error carries none
statusCode HTTP status — APIError only
response Raw API response body — APIError only, when non-empty
hint Remediation advice; optional, see below

hint appears only on API errors with status 401, 403, or 404, and is skipped on 404 when the message already names the missing resource. Treat it as optional in scripts.

Argument-parsing failures are the exception. An unknown option, a missing argument, or too many arguments prints a plain text usage error to stderr and exits 1 even under --json, so jq cannot parse it. The one parse failure that still emits an envelope is an unknown top-level command — bb bogus --json returns code 5002, while bb pr bogus --json prints error: unknown command 'bogus' as plain text.


For non-interactive scripts, use environment variables:

Terminal window
export BB_USERNAME=myuser
export BB_API_TOKEN=ATBB_token
bb auth login # Uses env vars
bb pr list -w workspace -r repo

See Environment Variables Reference for details.


API requests that fail with HTTP 429, 502, 503, or 504 are retried up to 3 times with exponential backoff. Add your own retry loop only if you need more than 3 attempts.

OAuth access tokens expire after 2 hours. The CLI refreshes them automatically — proactively before expiry and reactively on 401 responses. Long-running OAuth scripts never need to re-authenticate manually.


Workspace and repository detection reads the git remote of the current checkout. CI runners frequently check out with a non-Bitbucket remote, or no remote at all, so context detection fails there.

Terminal window
# Good - explicit and reliable
bb pr list -w myworkspace -r myrepo --json
# Bad - fails when the checkout has no Bitbucket remote
bb pr list --json

When making many sequential API calls, add short delays to avoid hitting rate limits:

#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
for pr_id in $(bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | jq -r '.pullRequests[].id'); do
bb pr view "$pr_id" -w "$WORKSPACE" -r "$REPO" --json
sleep 1
done

Approve all open pull requests (PRs) from a specific author:

#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
AUTHOR="trusted-bot"
echo "Finding PRs from $AUTHOR..."
pr_ids=$(bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | \
jq -r --arg author "$AUTHOR" '.pullRequests[] | select((.author.nickname // .author.display_name) == $author) | .id')
for pr_id in $pr_ids; do
echo "Approving PR #$pr_id..."
bb pr approve "$pr_id" -w "$WORKSPACE" -r "$REPO"
sleep 1
done
echo "Done!"

Generate a markdown report of open PRs:

#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
echo "# Open Pull Requests"
echo ""
echo "Generated: $(date)"
echo ""
bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | jq -r '.pullRequests[] |
"## PR #\(.id): \(.title)\n" +
"- **Author:** \(.author.display_name)\n" +
"- **Branch:** \(.source.branch.name) → \(.destination.branch.name)\n" +
"- **Created:** \(.created_on)\n" +
"- **Link:** \(.links.html.href)\n"'

Decline PRs not updated in 30 days:

#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
DAYS_OLD=30
cutoff=$(date -d "$DAYS_OLD days ago" -Iseconds 2>/dev/null || \
date -v-${DAYS_OLD}d -Iseconds) # macOS fallback
echo "Finding PRs older than $DAYS_OLD days..."
stale_prs=$(bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | \
jq -r --arg cutoff "$cutoff" '.pullRequests[] | select(.updated_on < $cutoff) | .id')
if [ -z "$stale_prs" ]; then
echo "No stale PRs found"
exit 0
fi
for pr_id in $stale_prs; do
echo "Declining stale PR #$pr_id..."
bb pr decline "$pr_id" -w "$WORKSPACE" -r "$REPO"
sleep 1
done