Skip to content

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.

Terminal window
# Full JSON output
bb pr list --json
# Project to a comma-separated field list
bb 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 JSON
bb repo view --json --no-color

Pass a comma-separated field list to project the output to just those fields, matching the way gh CLI handles --json:

Terminal window
bb pr list --json id,title,state
bb pr list --json id,title,author.display_name

Rules:

  • 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_name becomes 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 gh CLI semantics.

    pullRequests repositories snippets comments reviewers activities
    statuses files pipelines commits workspaces projects
    values
  • 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:

Terminal window
bb pipeline view 7 --json build_number
{
"build_number": null
}

Use --jq to reach into the envelope instead:

Terminal window
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:

Terminal window
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:

Terminal window
bb api /repositories/my-ws --paginate --json name,full_name

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.

Terminal window
# 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'
# Aggregate
bb pr list --json --jq '.count'
# Repository names
bb repo list --json --jq '.repositories[].full_name'
# Diff file paths
bb pr diff 42 --stat --json --jq '.files[].path'
# Auth status as a bare boolean
bb 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 URLs
bb pr list --json --jq '.pullRequests[].links.html.href'
# Project a nested URL out of a single resource
bb 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. --jq behaves like jq without -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
  • --jq requires --json. Using --jq alone 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 api is the exception: its output is already JSON, so bb 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 fields and a wrapper-style command are involved.

  • Invalid jq expressions exit non-zero with the underlying jq compiler error (error code 8001).

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
{
"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" } }
}
]
}
{
"workspace": "myworkspace",
"repoSlug": "myrepo",
"state": "OPEN",
"filters": { "mine": true },
"count": 1,
"pullRequests": [...]
}
{
"pullRequestId": 42,
"workspace": "myworkspace",
"repoSlug": "myrepo",
"summary": { "successful": 2, "failed": 0, "pending": 1 },
"statuses": [...]
}

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": {...}
}

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.)


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

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:

Terminal window
bb pr diff 42 --json | jq -r '.diff' > pr-42.patch
{
"workspace": "myworkspace",
"repoSlug": "myrepo",
"pullRequestId": 42,
"mode": "stat",
"filesChanged": 3,
"totalAdditions": 27,
"totalDeletions": 11,
"files": [
{ "path": "src/index.ts", "additions": 10, "deletions": 3 }
]
}
{
"workspace": "myworkspace",
"repoSlug": "myrepo",
"pullRequestId": 42,
"mode": "name-only",
"files": ["src/index.ts", "src/utils.ts"]
}
{
"workspace": "myworkspace",
"repoSlug": "myrepo",
"pullRequestId": 42,
"mode": "web",
"url": "https://bitbucket.org/myworkspace/myrepo/pull-requests/42/diff"
}

These commands have unique output shapes:

{
"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".

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" }
{
"configPath": "/Users/you/.config/bb/config.json",
"config": {
"username": "myuser",
"defaultWorkspace": "myworkspace",
"apiToken": "********",
"skipVersionCheck": false
}
}
{
"key": "defaultWorkspace",
"value": "myworkspace"
}

  • Prefer --json in scripts rather than parsing text output.
  • Do not rely on text formatting symbols (, table separators, colors).
  • Prefer the built-in --jq and --json fields over 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[].id rather than assuming a shape for the whole item.
  • pr list returns lightweight PR objects. Use pr view <id> for full details including participants and reviewers.

When a command fails, the CLI exits non-zero.

  • In normal mode, errors are plain text on stderr.
  • In --json mode, errors are compact JSON objects on stderr.

Example:

Terminal window
bb config get invalidKey --json 2>error.json
cat 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.

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)
20012005 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.