@hameddk/bitbucket-cloud-client
v0.1.0
Published
Pure REST API client for Bitbucket Cloud. Pull requests, tags, workspaces, repositories — with pagination and rate-limit handling. Caller supplies the access token. Zero deps. Node 18+.
Maintainers
Readme
@hameddk/bitbucket-cloud-client
Pure REST API client for Bitbucket Cloud.
- Pull requests, tags, workspaces, repositories, current user
- Auto-pagination via Bitbucket's
nextURL chain — both single-page and all-at-once forms nextUrlhost validation to prevent bearer-token leaks via tampered links- Rate-limit aware with configurable auto-retry (HTTP 429,
Retry-After) - Provider-agnostic auth — caller supplies an async access-token resolver
- Returns Bitbucket's raw response shape — no derived fields, no business logic
- Zero dependencies, ESM, Node ≥ 18
Status: 0.1.0 — early. The documented surface is stable.
Install
npm install @hameddk/bitbucket-cloud-clientQuick start
import { createBitbucketCloudClient } from '@hameddk/bitbucket-cloud-client';
const bb = createBitbucketCloudClient({
getAccessToken: () => oauth.getValidBitbucketAccessToken(),
options: {
pageSize: 50,
autoRetryRateLimit: true,
onRateLimit: (sec, attempt) => console.warn(`[bb] rate limited, retry ${attempt} in ${sec}s`),
},
});
// Pull all merged PRs in a repo
const merged = await bb.listPullRequestsAll({
workspace: 'your-workspace',
repo: 'your-repo',
state: 'MERGED',
});
// Tags / releases
const tags = await bb.listTagsAll({ workspace: 'your-workspace', repo: 'your-repo' });
// Current user (for /2.0/user)
const me = await bb.getCurrentUser();Public API
const bb = createBitbucketCloudClient({
getAccessToken, // () => Promise<string|null>
options: {
baseUrl: 'https://api.bitbucket.org/2.0',
pageSize: 50,
autoRetryRateLimit: true, // false | true | { maxRetries, maxDelayMs }
onRateLimit: (retryAfterSec, attempt) => {},
fetch: customFetch, // testing only
sleep: customSleep, // testing only
},
});Methods
Each paginated resource exposes a single-page form (returns { values, next })
and an auto-paginate form (returns the full array). Use the auto-paginate form
unless you need to handle pagination errors yourself.
| Method | Returns |
|---|---|
| listPullRequests({ workspace, repo, state, pageLen?, nextUrl? }) | { values, next } |
| listPullRequestsAll({ workspace, repo, state, pageLen?, maxTotal? }) | BitbucketPR[] |
| listTags({ workspace, repo, pageLen?, nextUrl? }) | { values, next } |
| listTagsAll({ workspace, repo, pageLen?, maxTotal? }) | BitbucketTag[] |
| listWorkspaces({ role?, pageLen?, nextUrl? }) | { values, next } |
| listWorkspacesAll({ role?, pageLen?, maxTotal? }) | Workspace[] |
| listRepositories(workspace, { pageLen?, nextUrl? }) | { values, next } |
| listRepositoriesAll(workspace, { pageLen?, maxTotal? }) | Repository[] |
| getCurrentUser() | raw user object |
state for pull requests must be 'OPEN' | 'MERGED' | 'DECLINED' | 'SUPERSEDED'.
The toolkit returns Bitbucket's raw response shapes under values. No
field renaming, no derived fields. Compute things like pr_cycle_time_hours
from created_on / closed_on in your business code.
Manual pagination
The single-page methods return { values, next }. The next field is a full
URL (or null). Pass it back as nextUrl to fetch the next page:
let page = await bb.listTags({ workspace: 'w', repo: 'r' });
while (page.next) {
console.log(page.values.length, 'tags');
page = await bb.listTags({ nextUrl: page.next });
}This is useful when you want to handle errors mid-pagination (e.g. return the results gathered so far).
Authentication
The toolkit doesn't do OAuth — pass a getAccessToken() resolver that returns
a valid bearer token (or null). Typical wiring with
@hameddk/oauth-toolkit:
const bb = createBitbucketCloudClient({
getAccessToken: () => bitbucketOAuth.getValidAccessToken(),
});The resolver is called on every request — no internal caching.
Security: nextUrl host validation
Bitbucket's pagination uses absolute next URLs returned in each response.
The toolkit validates that any URL it follows (whether passed in by the caller
as nextUrl or returned mid-pagination) matches the configured baseUrl host.
A mismatch throws BitbucketConfigError and the request is never sent.
Why this matters: if your application persists next URLs (e.g. for resumable
pagination state) and an attacker can tamper with that storage, they could
otherwise redirect a request to their own host — leaking the bearer token in
the Authorization header. The toolkit prevents this regardless of where the
URL came from.
// This throws BitbucketConfigError before any fetch happens:
bb.listPullRequests({ nextUrl: 'https://attacker.example/steal' });If you proxy Bitbucket through a custom host, set options.baseUrl to that
proxy and the validation will use the proxy host instead.
Rate limiting
⚠️ Behavior change vs. raw fetch. With
autoRetryRateLimit: true(the default), this toolkit transparently retries HTTP 429 responses. Most Bitbucket clients fail-fast on 429, so callers migrating from manualfetchcalls will see different behavior. The toolkit does not retry 401/403 — token-expiry errors still surface immediately asBitbucketAuthError. SetautoRetryRateLimit: falseto preserve fail-fast behavior.
Three forms:
| Value | Behavior |
|---|---|
| true (default) | Up to 2 retries, max 30s wait per retry. |
| false | No retry. First 429 throws BitbucketRateLimitError. |
| { maxRetries, maxDelayMs } | Custom limits. |
When 429 is received:
- Reads
Retry-After(seconds or HTTP-date) — falls back to 1s. - If the wait would exceed
maxDelayMs, throwsBitbucketRateLimitErrorimmediately. - After
maxRetries, throwsBitbucketRateLimitError. onRateLimit(retryAfterSec, attempt)callback fires before each retry. Throwing inside the callback is swallowed.
Errors
import {
BitbucketError, // base
BitbucketConfigError, // bad config / missing required arg / bad nextUrl host
BitbucketAuthError, // 401, 403, or getAccessToken returned null
BitbucketNotFoundError, // 404
BitbucketRateLimitError, // 429 after retries (or first 429 if disabled)
BitbucketApiError, // other 4xx/5xx
} from '@hameddk/bitbucket-cloud-client';All errors extend BitbucketError. The provider's response body is preserved
verbatim where available.
What this library does not do
- Doesn't perform OAuth — bring your own token resolver.
- Doesn't persist anything. No DB, no filesystem.
- Doesn't compute PR cycle-time, lead-time, or other metrics.
- Doesn't normalize Bitbucket's response shapes —
valuesis raw. - Doesn't dedupe or join paginated results across multiple states.
- Doesn't cap result sizes — pass
maxTotalif you want a cap.
License
MIT © 2026 Hamed Sattari
