JSON Output
All commands support the global --json flag, with optional field selection
and a built-in --jq filter modeled on the gh CLI.
In --json mode stdout carries the JSON document and nothing else. The
update-available banner and automatic-retry notices are suppressed, and errors
go to stderr — so bb … --json > out.json is safe.
Quick usage
Section titled “Quick usage”# Full JSON outputbb pr list --json
# Project to a comma-separated field listbb pr list --json id,title,state
# Filter through jq (no external jq binary required)bb pr list --json id,title,state --jq '.[] | select(.state == "OPEN") | .title'
# Disable color formatting and return JSONbb repo view --json --no-colorField selection (--json fields)
Section titled “Field selection (--json fields)”Pass a comma-separated field list to project the output to just those fields,
matching the way gh CLI handles --json:
bb pr list --json id,title,statebb pr list --json id,title,author.display_nameRules:
-
Bare
--json(no field list) keeps the full output — backwards compatible. -
Top-level field names become the keys of each result object verbatim, including dotted paths (
author.display_namebecomes the literal key"author.display_name"). -
Dotted paths traverse nested objects; missing intermediates yield
null. -
If the output is already an array, each item is projected.
-
If the output is an envelope object, the CLI looks for the first of these keys holding an array and projects that array instead, dropping the envelope — the result is a flat array. This matches
ghCLI semantics.pullRequests repositories snippets comments reviewers activitiesstatuses files pipelines commits workspaces projectsvalues -
If no key in that list holds an array, the envelope object itself is projected.
Example: bb pr list --json id,title,author.display_name
Section titled “Example: bb pr list --json id,title,author.display_name”[ { "id": 42, "title": "Add feature", "author.display_name": "Alice" }, { "id": 41, "title": "Fix bug", "author.display_name": "Bob" }]Before / after: shape change after projection
Section titled “Before / after: shape change after projection”bb pr list --json returns the full envelope with metadata and a named array:
{ "workspace": "myworkspace", "repoSlug": "myrepo", "state": "OPEN", "filters": { "mine": false }, "count": 2, "pullRequests": [ { "id": 42, "title": "Add feature", "state": "OPEN", "author": { "display_name": "Alice", "nickname": "alice" }, "links": { "html": { "href": "..." } } }, { "id": 41, "title": "Fix bug", "state": "OPEN", "author": {...}, "links": {...} } ]}The same command with --json id,title,state drops the envelope and projects each item to a flat array of just those fields:
[ { "id": 42, "title": "Add feature", "state": "OPEN" }, { "id": 41, "title": "Fix bug", "state": "OPEN" }]This is why --jq filters use .[] after projection (.[] | select(...)) but .pullRequests[] against the full envelope.
Trap: envelopes whose array key is not in the list
Section titled “Trap: envelopes whose array key is not in the list”bb pipeline view returns { workspace, repoSlug, pipeline, steps }. steps
is not a wrapper key, so nothing is unwrapped and the field list is applied to
the envelope — where build_number does not exist:
bb pipeline view 7 --json build_number{ "build_number": null}Use --jq to reach into the envelope instead:
bb pipeline view 7 --json --jq '.pipeline.build_number'bb pipeline logs (without --step) puts its array under steps too. bb snippet view --files returns files as an object, not an array, so it is
not unwrapped either.
The reverse trap also exists. bb pr view returns a bare pull request, and that
payload carries a reviewers array — a wrapper key — so bb pr view 42 --json links.html.href projects the reviewers instead of the pull request. Use --jq
against the full output:
bb pr view 42 --json --jq '.links.html.href'bb api --paginate merges every page into {"values": [...]}, and values
is a wrapper key, so projection does unwrap it:
bb api /repositories/my-ws --paginate --json name,full_nameBuilt-in jq (--jq)
Section titled “Built-in jq (--jq)”The --jq <expression> flag pipes the JSON output through an embedded jq
engine (jq-wasm) — no external jq
binary required, no bash pipe to fail on Windows.
# One value per line (strings come out quoted — see the raw-output note below)bb pr list --json --jq '.pullRequests[].title'
# Combine with field selection (jq runs after projection)bb pr list --json id,title,state --jq '.[] | select(.state == "OPEN") | .title'
# Aggregatebb pr list --json --jq '.count'
# Repository namesbb repo list --json --jq '.repositories[].full_name'
# Diff file pathsbb pr diff 42 --stat --json --jq '.files[].path'
# Auth status as a bare booleanbb auth status --json --jq '.authenticated'
# Project + format with jq string interpolation (external jq -r for raw TSV)bb pr list --json id,title,author.display_name \ | jq -r '.[] | "\(.id)\t\(.["author.display_name"])\t\(.title)"'
# Pull request URLsbb pr list --json --jq '.pullRequests[].links.html.href'
# Project a nested URL out of a single resourcebb repo view --json links.html.href --jq '.["links.html.href"]'
# Top open PRs by author with built-in jq (no projection — full shape)bb pr list --json --jq '.pullRequests | group_by(.author.display_name) | map({ author: .[0].author.display_name, count: length }) | sort_by(-.count)'Every expression above also works with an external jq binary — pipe the full
output instead: bb pr list --json | jq '.pullRequests[].title'.
Rules:
-
There is no raw-output switch.
--jqbehaves likejqwithout-r, so string results keep their quotes:Terminal window bb pr list --json --jq '.pullRequests[].title'# "Add feature"# "Fix bug"When a script needs unquoted text, pipe the full JSON to an external
jq -r:Terminal window bb pr list --json | jq -r '.pullRequests[].title'# Add feature# Fix bug -
--jqrequires--json. Using--jqalone exits non-zero with✗ --jq requires --json. That message prints as plain text on stderr, not as a JSON envelope, because JSON mode was never enabled. -
bb apiis the exception: its output is already JSON, sobb api /repositories/my-ws --jq '.values[].name'works without--json. -
jq runs after field projection, so
.refers to the projected shape — a flat array when both--json fieldsand a wrapper-style command are involved. -
Invalid jq expressions exit non-zero with the underlying jq compiler error (error code 8001).
Output patterns
Section titled “Output patterns”Pattern 1: collection (list commands)
Section titled “Pattern 1: collection (list commands)”Used by: pr list, repo list, pr comments list, pr activity,
snippet list, snippet comments list, pipeline list, commit list,
status list, workspace list, project list
Returns an envelope with metadata, a count, and a named array:
{ "workspace": "myworkspace", "repoSlug": "myrepo", "state": "OPEN", "filters": { "mine": false }, "count": 2, "pullRequests": [...]}Common envelope fields:
| Field | Type | Present in |
|---|---|---|
workspace |
string | All collections except workspace list (whose slugs live in the items) |
repoSlug |
string | Repository-scoped collections: pr list, pr comments list, pr activity, pipeline list, commit list, status list |
count |
number | All collections |
state |
string | pr list |
filters |
object | pr list, pr activity, pr comments list — always present, with an unset value inside ({"mine": false}, {"types": []}, {"resolution": null}) or {} |
commit |
string | status list |
sort |
string | pipeline list |
Collection key names:
| Command | Array key |
|---|---|
pr list |
pullRequests |
repo list |
repositories |
pr comments list |
comments |
pr activity |
activities |
pipeline list |
pipelines |
commit list |
commits |
status list |
statuses |
workspace list |
workspaces |
project list |
projects |
snippet list |
snippets |
snippet comments list |
comments |
Three commands return collections without going through the shared list
plumbing. They are not paginated and have no --limit/--all:
| Command | Shape |
|---|---|
pr reviewers list |
{ workspace, repoSlug, pullRequestId, count, reviewers } |
repo default-reviewers list |
{ workspace, repoSlug, mode, count, reviewers } |
pr checks |
{ pullRequestId, workspace, repoSlug, summary, statuses } — no count |
Example: bb pr list --json
Section titled “Example: bb pr list --json”{ "workspace": "myworkspace", "repoSlug": "myrepo", "state": "OPEN", "filters": { "mine": false }, "count": 2, "pullRequests": [ { "id": 42, "title": "Add feature", "state": "OPEN", "draft": false, "author": { "display_name": "Alice", "nickname": "alice" }, "source": { "branch": { "name": "feature/add-feature" } }, "destination": { "branch": { "name": "main" } }, "created_on": "2026-01-15T10:00:00.000000+00:00", "updated_on": "2026-01-16T14:30:00.000000+00:00", "links": { "html": { "href": "https://bitbucket.org/myworkspace/myrepo/pull-requests/42" } } } ]}Example: bb pr list --mine --json
Section titled “Example: bb pr list --mine --json”{ "workspace": "myworkspace", "repoSlug": "myrepo", "state": "OPEN", "filters": { "mine": true }, "count": 1, "pullRequests": [...]}Example: bb pr checks 42 --json
Section titled “Example: bb pr checks 42 --json”{ "pullRequestId": 42, "workspace": "myworkspace", "repoSlug": "myrepo", "summary": { "successful": 2, "failed": 0, "pending": 1 }, "statuses": [...]}Pattern 2a: bare resource
Section titled “Pattern 2a: bare resource”Used by: pr view, pr create, pr edit, pr comments view, repo view,
repo create, snippet view (no --file/--files), snippet create,
snippet edit
Returns the raw Bitbucket API resource object directly, with no envelope:
{ "type": "pullrequest", "id": 42, "title": "Add feature", "state": "OPEN", "author": {...}, "source": {...}, "destination": {...}, "participants": [...], "links": {...}}Pattern 2b: enveloped resource
Section titled “Pattern 2b: enveloped resource”The rest of the single-resource commands wrap the payload in a context envelope. Read the resource from the named key, not from the root:
| Command | Shape |
|---|---|
commit view |
{ workspace, repoSlug, commit } |
project view, project create |
{ workspace, project } |
workspace view |
{ workspace } (the workspace object) |
status set |
{ workspace, repoSlug, commit, status } |
pipeline view |
{ workspace, repoSlug, pipeline, steps } |
pipeline run |
{ workspace, repoSlug, pipeline } |
pipeline logs --step <uuid> |
{ workspace, repoSlug, pipelineId, stepUuid, log } |
pipeline logs (no --step) |
{ workspace, repoSlug, pipelineId, count, steps } |
snippet view --file <name> |
{ file, content } |
snippet view --files |
{ snippet, files } (files is an object keyed by filename) |
pipeline view and pipeline logs put their array under steps, which is not
a projection wrapper key — see the trap above.
Pattern 3: action (approve/merge/decline commands)
Section titled “Pattern 3: action (approve/merge/decline commands)”Used by: pr approve, pr decline, pr merge, pr ready, pr comments add/edit/delete/reply/resolve/unresolve, pr reviewers add/remove, auth logout
Returns a success indicator plus resource identifiers:
{ "success": true, "pullRequestId": 42}Some action commands include the full resource in the response:
{ "success": true, "pullRequestId": 42, "pullRequest": {...}}Two action-style commands do not emit success — branch on their own key
instead:
auth login→{ "authenticated": true, "method": "oauth" | "api_token", "user": {...} }pipeline stop→{ "workspace": "...", "repoSlug": "...", "pipelineId": "...", "stopped": true }
(auth logout does emit success: { "authenticated": false, "success": true },
plus "revokeFailed": true when the OAuth token could not be revoked.)
Pattern 4: mode-based (diff command)
Section titled “Pattern 4: mode-based (diff command)”Used by: pr diff (with --stat, --web, --name-only, or default diff mode)
Returns context metadata plus mode-specific data. Every mode includes the same
context envelope (workspace, repoSlug, pullRequestId, mode); the
remaining fields depend on the mode.
| Mode | Trigger | Mode-specific fields |
|---|---|---|
diff |
(default) | diff (string — the raw unified diff text) |
stat |
--stat |
filesChanged, totalAdditions, totalDeletions, files[] |
name-only |
--name-only |
files[] (string array of paths) |
web |
--web |
url |
bb pr diff 42 --json (default mode)
Section titled “bb pr diff 42 --json (default mode)”The default mode returns the raw unified diff as a single string under diff.
Newlines are preserved, so the value is suitable for piping to git apply or
writing to a .patch file.
{ "workspace": "myworkspace", "repoSlug": "myrepo", "pullRequestId": 42, "mode": "diff", "diff": "diff --git a/src/index.ts b/src/index.ts\nindex abc123..def456 100644\n--- a/src/index.ts\n+++ b/src/index.ts\n@@ -1,3 +1,4 @@\n import { foo } from './foo';\n+import { bar } from './bar';\n \n export const main = () => {\n"}Extract just the diff text in scripts. Use an external jq -r here — the
built-in --jq would emit the value as a quoted, escaped JSON string:
bb pr diff 42 --json | jq -r '.diff' > pr-42.patchbb pr diff 42 --stat --json
Section titled “bb pr diff 42 --stat --json”{ "workspace": "myworkspace", "repoSlug": "myrepo", "pullRequestId": 42, "mode": "stat", "filesChanged": 3, "totalAdditions": 27, "totalDeletions": 11, "files": [ { "path": "src/index.ts", "additions": 10, "deletions": 3 } ]}bb pr diff 42 --name-only --json
Section titled “bb pr diff 42 --name-only --json”{ "workspace": "myworkspace", "repoSlug": "myrepo", "pullRequestId": 42, "mode": "name-only", "files": ["src/index.ts", "src/utils.ts"]}bb pr diff 42 --web --json
Section titled “bb pr diff 42 --web --json”{ "workspace": "myworkspace", "repoSlug": "myrepo", "pullRequestId": 42, "mode": "web", "url": "https://bitbucket.org/myworkspace/myrepo/pull-requests/42/diff"}Pattern 5: auth & config (custom)
Section titled “Pattern 5: auth & config (custom)”These commands have unique output shapes:
bb auth status --json
Section titled “bb auth status --json”{ "authenticated": true, "method": "oauth", "user": { "username": "myuser", "displayName": "My Name", "accountId": "70121:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }, "defaultWorkspace": "myworkspace", "tokenExpiresAt": 1767225600}tokenExpiresAt (a Unix timestamp in seconds) is present only for OAuth
credentials. When no credentials are stored the whole payload is:
{ "authenticated": false }method is spelled differently by the two commands. auth status reports the
stored value, "basic" or "oauth"; auth login reports "api_token" or
"oauth" for the same credentials. Match on "oauth" versus everything else
rather than on the literal "api_token".
bb auth token --json
Section titled “bb auth token --json”OAuth credentials return the access token:
{ "token": "<access token>", "type": "bearer" }API-token credentials return the base64 of username:apiToken:
{ "token": "<base64 of username:apiToken>", "type": "basic" }bb config list --json
Section titled “bb config list --json”{ "configPath": "/Users/you/.config/bb/config.json", "config": { "username": "myuser", "defaultWorkspace": "myworkspace", "apiToken": "********", "skipVersionCheck": false }}bb config get <key> --json
Section titled “bb config get <key> --json”{ "key": "defaultWorkspace", "value": "myworkspace"}Scripting notes
Section titled “Scripting notes”- Prefer
--jsonin scripts rather than parsing text output. - Do not rely on text formatting symbols (
✓, table separators, colors). - Prefer the built-in
--jqand--json fieldsover external pipes — they avoid an extra binary dependency and behave identically across platforms. - Envelope keys (
count,pullRequests,repositories,success) are owned by the CLI and stable. Item fields come straight from the Bitbucket API, so read.pullRequests[].idrather than assuming a shape for the whole item. pr listreturns lightweight PR objects. Usepr view <id>for full details includingparticipantsandreviewers.
Errors
Section titled “Errors”When a command fails, the CLI exits non-zero.
- In normal mode, errors are plain text on stderr.
- In
--jsonmode, errors are compact JSON objects on stderr.
Example:
bb config get invalidKey --json 2>error.jsoncat error.json# {"name":"BBError","code":4003,"message":"Unknown config key 'invalidKey'. Valid keys: username, defaultWorkspace, skipVersionCheck, versionCheckInterval, prCreateIncludeDefaultReviewers","context":{"key":"invalidKey"}}Error JSON fields:
| Field | Type | Description |
|---|---|---|
name |
string | Error class name (usually BBError; can also be APIError, AuthError, GitError, ValidationError) |
code |
number | Numeric error code |
message |
string | Human-readable message |
context |
object | Optional structured metadata. Shape depends on code — see the table below |
hint |
string | Optional. A single actionable next step, present only for 401, 403, and 404 failures where the CLI has advice to give. In human-readable mode the same text prints as a dimmed continuation line beneath the message. |
statusCode |
number | APIError only. The upstream HTTP status. |
response |
object | APIError only, and only when the upstream response had a body. The raw Bitbucket error payload. |
cause (the underlying Error) and stack traces are never exposed in --json
mode. Do not treat the field list above as closed: new optional keys such as
hint may be added, so parse defensively rather than asserting an exact key
set.
context fields by error code
Section titled “context fields by error code”The context object is omitted when there’s nothing useful to attach. When
present, the shape is:
| Code | Typical context keys |
|---|---|
1001 AUTH_REQUIRED |
(none) |
1002 AUTH_INVALID |
status (HTTP status, OAuth verification failures only) |
1003 AUTH_EXPIRED |
status (HTTP status from the token endpoint) |
2001–2005 API_* |
status, method, url — plus the top-level statusCode and response fields described above. url is the request path only; pagination and filter parameters the CLI sends are not part of it, though a query string you write yourself (bb api '/repositories/acme?role=owner') is |
3001 GIT_NOT_REPOSITORY |
(none — never thrown) |
3002 GIT_COMMAND_FAILED |
command, exitCode |
3003 GIT_REMOTE_NOT_FOUND |
remote |
4001 CONFIG_READ_FAILED |
(none) |
4002 CONFIG_WRITE_FAILED |
(none) |
4003 CONFIG_INVALID_KEY |
key |
5001 VALIDATION_REQUIRED |
field (when thrown by ValidationError) |
5002 VALIDATION_INVALID |
varies — usually { key, value }, the offending option name, or the rejected ID |
5003 FILE_NOT_FOUND |
file (snippet create/edit/view), bodyFile (pr edit --body-file), or path (bb api -F key=@file / --input). snippet view --file also attaches available, the list of filenames in the snippet |
6001 CONTEXT_REPO_NOT_FOUND |
(none) |
6002 CONTEXT_WORKSPACE_NOT_FOUND |
(none) |
7001 NETWORK_ERROR |
(usually none; OAuth revoke failures include status, body) |
8001 JQ_FAILED |
expression, optional exitCode |
8002 JSON_FORMAT_INVALID |
(none) |
9001 COMPLETION_INSTALL_FAILED |
(none) |
9002 COMPLETION_UNINSTALL_FAILED |
(none) |
9999 UNKNOWN |
(none) |
In automation, always check the process exit code before parsing stdout JSON.