Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@hameddk/bitbucket-cloud-client

Pure REST API client for Bitbucket Cloud.

  • Pull requests, tags, workspaces, repositories, current user
  • Auto-pagination via Bitbucket's next URL chain — both single-page and all-at-once forms
  • nextUrl host 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-client

Quick 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 manual fetch calls will see different behavior. The toolkit does not retry 401/403 — token-expiry errors still surface immediately as BitbucketAuthError. Set autoRetryRateLimit: false to 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, throws BitbucketRateLimitError immediately.
  • After maxRetries, throws BitbucketRateLimitError.
  • 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 — values is raw.
  • Doesn't dedupe or join paginated results across multiple states.
  • Doesn't cap result sizes — pass maxTotal if you want a cap.

License

MIT © 2026 Hamed Sattari

About

Bitbucket Cloud REST API client. Pull requests, tags, workspaces, repositories. Pluggable auth via async token resolver, configurable rate-limit retry, host-validated pagination. Zero dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages