Remove Image Backgrounds in C# / .NET with an API (HttpClient, MultipartFormDataContent)
In .NET the fastest reliable way to remove an image background is an API call: HttpClient already does multipart, System.Text.Json handles the response, and IHttpClientFactory plus Polly turns it into something production-safe. This tutorial builds a small typed client for the bgclear API, wires it into ASP.NET Core, and adds an endpoint so your Blazor or JavaScript front end never sees the key. remove.bg users: the request fields are identical, which is useful with remove.bg's API ending on 1 December 2026.
In this guide
A typed client
using System.Net.Http.Headers;
using System.Text.Json;
public sealed class BgClearClient(HttpClient http)
{
// http.BaseAddress = https://www.bgclear.ai/api/v1/ and the Authorization
// header are configured in Program.cs (see below).
public async Task<byte[]> RemoveBackgroundAsync(Stream image, string fileName,
string size = "auto", string format = "png", string? bgColor = null,
string? idempotencyKey = null, CancellationToken ct = default)
{
using var form = new MultipartFormDataContent();
var file = new StreamContent(image);
file.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
form.Add(file, "image_file", fileName);
form.Add(new StringContent(size), "size"); // preview (free) | full | auto
form.Add(new StringContent(format), "format"); // png | webp | jpg
if (bgColor is not null) form.Add(new StringContent(bgColor), "bg_color");
using var req = new HttpRequestMessage(HttpMethod.Post, "remove") { Content = form };
if (idempotencyKey is not null) req.Headers.Add("Idempotency-Key", idempotencyKey);
using var res = await http.SendAsync(req, ct);
if (!res.IsSuccessStatusCode)
{
var err = await JsonSerializer.DeserializeAsync<ErrorEnvelope>(await res.Content.ReadAsStreamAsync(ct), cancellationToken: ct);
throw new BgClearException((int)res.StatusCode, err?.Error?.Code ?? "unknown", err?.Error?.Message ?? "");
}
Console.WriteLine($"credits remaining: {res.Headers.GetValues("X-Credits-Remaining").FirstOrDefault()}");
return await res.Content.ReadAsByteArrayAsync(ct);
}
public async Task<RemoveResult> RemoveByUrlAsync(string imageUrl, string size = "auto", CancellationToken ct = default)
{
using var req = new HttpRequestMessage(HttpMethod.Post, "remove")
{
Content = JsonContent.Create(new { image_url = imageUrl, size, format = "webp" })
};
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
using var res = await http.SendAsync(req, ct);
res.EnsureSuccessStatusCode();
return (await res.Content.ReadFromJsonAsync<RemoveResult>(cancellationToken: ct))!;
}
}
public record RemoveResult(string Id, string Url, int Width, int Height, int Credits_Charged, int Credits_Remaining, int Processing_Ms);
public record ErrorEnvelope(ErrorBody? Error);
public record ErrorBody(string Code, string Message, string Docs);
public class BgClearException(int status, string code, string message) : Exception($"{status} {code}: {message}")
{
public int Status { get; } = status;
public string Code { get; } = code;
}RemoveByUrlAsync returns a hosted result URL valid for 24 hours — handy when the next step is a CDN upload rather than a local file.
Registration with IHttpClientFactory and Polly
// Program.cs
builder.Services.AddHttpClient<BgClearClient>(client =>
{
client.BaseAddress = new Uri("https://www.bgclear.ai/api/v1/");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", builder.Configuration["BgClear:ApiKey"]);
client.Timeout = TimeSpan.FromSeconds(90);
})
.AddStandardResilienceHandler(o =>
{
// 429 and 5xx are retried with backoff; 4xx are not.
o.Retry.ShouldHandle = args => ValueTask.FromResult(
args.Outcome.Result is { StatusCode: System.Net.HttpStatusCode.TooManyRequests }
|| (int?)args.Outcome.Result?.StatusCode >= 500);
o.Retry.MaxRetryAttempts = 4;
o.Retry.Delay = TimeSpan.FromSeconds(5);
});Keep the key in user-secrets or an environment variable (BgClear__ApiKey), never in appsettings.json committed to git. The rate limit is 60 requests a minute per key; the resilience handler's backoff covers a burst.
A minimal API endpoint for your front end
app.MapPost("/api/cutout", async (IFormFile image, BgClearClient bgclear, CancellationToken ct) =>
{
if (image.Length > 25 * 1024 * 1024) return Results.BadRequest("Max 25 MB");
await using var stream = image.OpenReadStream();
try
{
var png = await bgclear.RemoveBackgroundAsync(stream, image.FileName,
size: "auto", idempotencyKey: $"{image.Length}-{image.FileName}", ct: ct);
return Results.File(png, "image/png");
}
catch (BgClearException e) when (e.Status == 402)
{
return Results.Problem("Out of credits", statusCode: 402);
}
})
.DisableAntiforgery()
.RequireAuthorization();The RequireAuthorization() matters: without it, anyone who finds the endpoint spends your credits. Images above 4 megapixels at full size are rejected synchronously; send those to jobs and poll jobs/{id}, or pass an X-Callback-Url header — the bulk guide has the pattern.
Cost and testing
size=preview is free and unlimited — use it in integration tests. Full-resolution images cost one credit ($9 for 100, $39 for 500, $129 for 2,000; credits never expire, failures never charged — pricing). Replacing remove.bg? Same fields, new URL and header: migration page.
Frequently asked questions
Is there a NuGet package?
No — the typed client above is the whole thing, and you can generate one from the OpenAPI document at /api/v1/openapi.json with NSwag or Kiota if you prefer.
Can I use this from a Blazor WebAssembly app directly?
Only through your own server endpoint; the key must never reach the browser.
Does it work on .NET Framework?
Yes with HttpClient and MultipartFormDataContent (available since .NET Framework 4.5); drop the primary-constructor syntax and the resilience handler.
What does a full-resolution image cost?
One credit — $0.065 to $0.09 depending on pack; previews are free.