Modernized C# client for Bitbucket Server (Stash) REST API.
Development setup (including the pre-commit formatting hook) is documented in CONTRIBUTING.md.
Fork notice — This is an actively maintained fork of lvermeulen/Bitbucket.Net, which appears to be abandoned (last release 2020). The 1.0.0 API surface is stable; breaking changes follow semver. The library is used in production by the author (as the backend for an MCP Server talking to on-prem Bitbucket Server), but not every endpoint has been verified against a live instance. Contributions, bug reports, and feedback are welcome.
- .NET 10 target (dropped .NET Framework / .NET Standard)
System.Text.Jsonwith source generation (no runtime reflection)CancellationTokenon every async methodIAsyncEnumerablestreaming for paginated endpoints- Streaming diffs and raw file content
- Typed exception hierarchy (
BitbucketNotFoundException, etc.) IHttpClientFactory/ DI-friendly constructorsIDisposablewith ownership trackingIBitbucketClientdecomposed into 12 domain-specific sub-interfaces- Fluent query builders for pull requests, commits, branches, and projects
- Dedicated request DTOs for write operations
- Input validation on all public API methods
- OpenTelemetry tracing via
ActivitySource - Bitbucket Server 9.0+ blocker-comment (task) support with legacy fallback
- Flurl.Http 4.x
If you're looking for Bitbucket Cloud API, try this repository.
dotnet add package BitbucketServer.Netvar client = new BitbucketClient("https://bitbucket.example.com", "username", "password");var client = new BitbucketClient("https://bitbucket.example.com", () => GetAccessToken());BitbucketClient implements IDisposable. Clients created with a URL
own the underlying HTTP connection and dispose it:
using var client = new BitbucketClient("https://bitbucket.example.com", "user", "pass");
var projects = await client.GetProjectsAsync();When you inject an HttpClient or IFlurlClient, the caller retains
ownership; the client will not dispose it.
For production scenarios, you can inject an externally managed HttpClient to leverage IHttpClientFactory for connection pooling, resilience policies, and centralized configuration.
The simplest approach uses Microsoft.Extensions.Http.Resilience which provides
retry, circuit breaker, and timeout out of the box:
// Requires: dotnet add package Microsoft.Extensions.Http.Resilience
services.AddHttpClient<BitbucketClient>(client =>
{
client.Timeout = TimeSpan.FromMinutes(2);
})
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Retry.Delay = TimeSpan.FromSeconds(1);
options.Retry.BackoffType = DelayBackoffType.Exponential;
options.CircuitBreaker.FailureRatio = 0.5;
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(30);
});
// Register IBitbucketClient for dependency injection
services.AddSingleton<IBitbucketClient>(sp =>
{
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(nameof(BitbucketClient));
return new BitbucketClient(
httpClient,
"https://bitbucket.example.com",
() => sp.GetRequiredService<ITokenProvider>().GetToken());
});For fine-grained control over which responses trigger retries:
services.AddHttpClient<BitbucketClient>(client =>
{
client.Timeout = TimeSpan.FromMinutes(2);
})
.AddResilienceHandler("bitbucket", builder =>
{
builder
.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromSeconds(1),
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(r => r.StatusCode == HttpStatusCode.TooManyRequests
|| r.StatusCode >= HttpStatusCode.InternalServerError)
})
.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(15),
})
.AddTimeout(TimeSpan.FromSeconds(30));
});For fine-grained control over Flurl's configuration:
services.AddSingleton<IFlurlClientCache>(sp => new FlurlClientCache()
.Add("Bitbucket", "https://bitbucket.example.com", builder => builder
.WithSettings(s => s.Timeout = TimeSpan.FromMinutes(5))
.WithHeader("X-Custom-Header", "value")));
services.AddSingleton<IBitbucketClient>(sp =>
{
var flurlClient = sp.GetRequiredService<IFlurlClientCache>().Get("Bitbucket");
return new BitbucketClient(flurlClient, () => GetToken());
});For memory-efficient processing of large result sets, use the streaming variants:
// Stream projects without buffering all pages in memory
await foreach (var project in client.GetProjectsStreamAsync())
{
Console.WriteLine(project.Name);
}
// With cancellation support
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
await foreach (var pr in client.GetPullRequestsStreamAsync("PROJ", "repo", cancellationToken: cts.Token))
{
await ProcessPullRequestAsync(pr);
}
// Stream PR activities
await foreach (var activity in client.GetPullRequestActivitiesStreamAsync(
"PROJ", "repo", pullRequestId: 42))
{
ProcessActivity(activity);
}
// Stream dashboard PRs
await foreach (var pr in client.GetDashboardPullRequestsStreamAsync())
{
Console.WriteLine($"#{pr.Id}: {pr.Title}");
}For endpoints with many optional filters, query builders provide a typed alternative to the flat method signatures:
var openPRs = await client.PullRequests("PROJ", "repo")
.InState(PullRequestStates.Open)
.OrderBy(PullRequestOrders.Newest)
.PageSize(25)
.GetAsync();
// Streaming variant
await foreach (var pr in client.PullRequests("PROJ", "repo")
.InState(PullRequestStates.Open)
.StreamAsync())
{
Console.WriteLine(pr.Title);
}Builders are available for pull requests, commits, branches, and projects. The original flat methods still work and are not deprecated.
Typed exceptions give you precise control over error handling:
try
{
var repo = await client.GetRepositoryAsync("PROJ", "repo");
}
catch (BitbucketNotFoundException ex)
{
Console.WriteLine($"Repository not found: {ex.Context}");
}
catch (BitbucketAuthenticationException)
{
Console.WriteLine("Invalid credentials");
}
catch (BitbucketForbiddenException ex)
{
Console.WriteLine($"Access denied: {ex.Message}");
}
catch (BitbucketApiException ex)
{
Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
}Performance benchmarks are available in the benchmarks/ folder using BenchmarkDotNet:
cd benchmarks/Bitbucket.Net.Benchmarks
dotnet run -c ReleaseSee benchmarks/README.md for detailed instructions.
- Audit
- Project Events
- Repository Events
- Branches
- Create Branch
- Delete Branch
- Branch Info
- Branch Model
- Builds
- Commits Build Stats
- Commit Build Stats
- Commit Build Status
- Associate Build Status
- Comment Likes
- Repository Comment Likes
- Pull Request Comment Likes
- Core
- Admin
- Groups
- Users
- Cluster
- License
- Mail Server
- Permissions
- Pull Requests
- Application Properties
- Dashboard
- Groups
- Hooks
- Inbox
- Logs
- Markup
- Profile
- Projects
- Projects
- Permissions
- Repos
- Repos
- Branches
- Browse
- Changes
- Commits
- Compare
- Diff
- Files
- Last Modified
- Participants
- Permissions
- Pull Requests
- Raw
- Settings
- Tags
- Webhooks
- Settings
- Repos
- Tasks
- Users
- Admin
- Default Reviewers
- Project Default Reviewers
- Repository Default Reviewers
- Git
- JIRA
- Create JIRA Issue
- Get Commits For JIRA Issue
- Get JIRA Issues For Commits
- Personal Access Tokens
- Ref Restrictions
- Project Restrictions
- Repository Restrictions
- Repository Ref Synchronization
- SSH