DEVELOPERS / API V1
Background removal API
Upload, remove, edit, export, and track images with one async API.
Quick start
The API is async by default. Send an image, get a job, then poll, stream events, or receive a signed webhook. Local base URL:
http://127.0.0.1:3001
Hosted workspaces receive an HTTPS URL. Bodies are JSON unless an endpoint uses multipart or binary data.
curl -X POST http://127.0.0.1:3001/v1/background-removals \
-H "Authorization: Bearer $BLANKGROUND_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Prefer: wait=8" \
-F "image=@product.jpg" \
-F 'format=png' \
-F 'recipe={"version":1,"background":{"type":"transparent"}}'
A fast job returns 200 OK. After eight seconds, you get 202 Accepted and a job URL in Location.
Service documents and health
| Method and path | Purpose |
|---|---|
GET /openapi.json | OpenAPI 3.1 contract used to generate SDKs |
GET /health/live | Confirms that the API process is alive |
GET /health/ready | Reports whether required processing dependencies are ready |
Authentication
Protected requests use a bearer API key:
Authorization: Bearer bg_live_••••••••••••••••••••
Keys appear once, are stored as hashes, and support scopes and expiry. Keep them server-side and out of URLs, logs, browsers, and support requests. Anonymous removal uses a signed device cookie and excludes full resolution, batches, keys, webhooks, and projects.
Common scopes:
| Scope | Allows |
|---|---|
images:write | Uploads, removal jobs, batches, refinements, exports |
jobs:read | Job, event, result, history, and credit reads |
webhooks:write | Webhook endpoint management |
keys:write | API-key management |
Request conventions
Idempotency
Creation endpoints require Idempotency-Key. Retry the same operation with the same key and body. Different input returns 409 idempotency_conflict.
Bounded waiting
Use Prefer: wait=8 to wait up to eight seconds. Success returns 200; otherwise you get 202.
States and stages
States: queued, processing, succeeded, failed, canceled. Stages: preparing, segmenting, refining, rendering, storing, complete.
Limits
| Limit | Value |
|---|---|
| Input formats | JPEG, PNG, WebP, AVIF |
| Minimum dimensions | 32×32 |
| Maximum pixels | 25 megapixels |
| Maximum bytes | 50 MB |
| Batch size | 100 images |
| Remote redirects | 5 |
| AI refinements | 20 per project |
| Result URL | 10 minutes |
| Binary retention | 24 hours after terminal completion |
Source URLs must be public HTTP or HTTPS. Private networks, metadata services, credentials, unsafe DNS, and rebinding are blocked.
Uploads
Use direct uploads for large files and batches. Local mode returns an API path; persistent environments may return a presigned storage URL.
/v1/uploadsCreates a short-lived upload reservation. No body is required.
{
"id": "019c…",
"uploadUrl": "/v1/uploads/019c…/content",
"expiresAt": "2026-08-05T12:15:00.000Z"
}
/v1/uploads/{id}/contentUpload raw bytes with Content-Type. The server still validates the decoded image.
curl -X PUT "$BASE_URL/v1/uploads/$UPLOAD_ID/content" \
-H "Authorization: Bearer $BLANKGROUND_API_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @product.jpg
/v1/uploads/{id}/completeInspect and confirm the upload. Returns verified dimensions, media type, and ready status.
Background removals
/v1/background-removalsAccepts multipart data with image, or JSON with exactly one of uploadId and sourceUrl.
Multipart parameters:
| Name | Type | Required | Description |
|---|---|---|---|
image | file | yes | JPEG, PNG, WebP, or AVIF |
format | string | no | png, webp, jpeg, zip, or psd; default png |
recipe | JSON string | no | Versioned composition recipe |
JSON request:
{
"uploadId": "019c…",
"format": "webp",
"recipe": {
"version": 1,
"background": { "type": "transparent" },
"crop": true,
"margin": 48,
"width": 1600,
"height": 1600,
"scale": 0.92,
"positionX": 0.5,
"positionY": 0.5,
"quality": 88,
"shadow": {
"color": "#000000",
"opacity": 0.22,
"blur": 24,
"distance": 14,
"angle": 90
}
}
}
Response:
{
"id": "019c…",
"projectId": "019c…",
"workspaceId": "workspace:…",
"status": "queued",
"stage": "preparing",
"format": "png",
"createdAt": "2026-08-05T12:00:00.000Z"
}
Jobs and results
/v1/jobs/{id}Returns status, stage, timestamps, model version, result metadata, or a stable error.
/v1/jobsReturns workspace history. Anonymous history requires a known job ID in the same device session.
/v1/jobs/{id}/eventsReturns a text/event-stream. Event fields include id, type, occurredAt, workspaceId, aggregateId, correlationId, and data.
id: 019c…
event: image.job.completed.v1
data: {"id":"019c…","type":"image.job.completed.v1","data":{"jobId":"019c…"}}
Reconnect with the last event ID. After a gap, reconcile with GET /v1/jobs/{id}.
/v1/jobs/{id}/cancelCancel queued jobs immediately. Active jobs may briefly remain processing. Cancellation releases the credit hold.
/v1/jobs/{id}/resultReturns the result or a signed ten-minute URL. Expired files return 410 expired_asset; job metadata remains.
Batches
/v1/batchesCreates up to 100 removal jobs from completed upload IDs. Requires authentication and Idempotency-Key.
{
"uploadIds": ["019c…", "019d…"],
"format": "jpeg",
"recipe": {
"version": 1,
"background": { "type": "color", "color": "#ffffff" },
"crop": true,
"margin": 64,
"width": 1600,
"height": 1600
}
}
The response is 202 Accepted with a batch ID, total, status, and child jobs.
/v1/batches/{id}Returns batch progress and child jobs. Partial failure keeps successful results.
Projects and editing
/v1/projects/{id}/revisionsCreates an immutable revision. A stale baseRevisionId returns 409 revision_conflict.
{
"baseRevisionId": "019c…",
"strokes": [
{
"tool": "restore",
"points": [
[0.42, 0.17],
[0.43, 0.18]
],
"size": 0.02,
"hardness": 0.7,
"opacity": 1
}
],
"recipe": { "version": 1, "background": { "type": "transparent" } }
}
Stroke coordinates and sizes are normalized to the source image so server-side rasterization is deterministic at original resolution.
/v1/projects/{id}/refinementsQueues point or box refinement. It shares the workspace slot, costs no extra credit, and is capped at 20 per project.
{
"points": [
{ "x": 0.42, "y": 0.33, "label": "foreground" },
{ "x": 0.61, "y": 0.45, "label": "background" }
],
"baseRevisionId": "019c…"
}
/v1/projects/{id}/exportsRenders a project revision with an optional recipe and png, webp, jpeg, zip, or psd format. Renders do not consume another removal credit.
Credits
/v1/creditsWorkspace response:
{
"kind": "workspace",
"limit": 100,
"available": 87,
"held": 2,
"used": 11,
"periodEndsAt": "2026-09-01T00:00:00.000Z"
}
Anonymous responses show the 7-per-day balance and UTC reset. Credits are held on start, charged on success, and released on failure or cancellation.
/v1/deviceDeletes this browser’s anonymous cookie. It does not delete an account or reset the allowance.
Email authentication
/v1/auth/email/startBody: { "email": "person@example.com" }. Sends a six-digit, ten-minute, single-use code. Responses do not reveal whether an email already exists.
/v1/auth/email/verifyBody: { "email": "person@example.com", "code": "123456" }. Creates the default workspace when needed and returns the authenticated session representation. Five failed attempts invalidate the challenge.
API keys
/v1/api-keysLists key metadata—ID, name, prefix, scopes, creation, expiry, and last use—never the secret.
/v1/api-keys{
"name": "Catalog production",
"scopes": ["images:write", "jobs:read"],
"expiresAt": "2027-01-01T00:00:00.000Z"
}
Returns 201 Created and the secret once. Store it immediately.
/v1/api-keys/{id}Revokes the key now. For rotation, create the new key before deleting the old one.
Webhooks
/v1/webhooksLists workspace webhook endpoints and delivery configuration without returning signing secrets.
/v1/webhooks{
"url": "https://example.com/hooks/blankground",
"events": ["image.job.completed.v1", "image.job.failed.v1"]
}
The signing secret is displayed once. Public endpoints must use HTTPS.
/v1/webhooks/{id}Disables delivery and removes the endpoint.
Verifying a delivery
Headers identify the timestamp, delivery ID, and signature. Compute HMAC-SHA256 over:
{timestamp}.{deliveryId}.{rawRequestBody}
Compare in constant time, reject stale timestamps, and store delivery IDs against replay. Return 2xx after durable acceptance. Retries continue for 24 hours.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(
secret: string,
timestamp: string,
deliveryId: string,
rawBody: string,
supplied: string,
) {
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${deliveryId}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(supplied));
}
Device authorization
Photoshop and other limited-input clients use a device-code flow.
/v1/device-authorizationsBody: { "client": "photoshop" }. Returns deviceCode, human-readable userCode, verification URI, expiry, and polling interval.
/v1/device-authorizations/{code}/approveCalled by an authenticated browser after the user confirms the code and workspace. The code is short lived and single use.
/v1/device-authorizations/tokenBody: { "deviceCode": "…" }. Returns 428 authorization_pending until approval, then returns access and refresh credentials. Clients must respect the polling interval.
Errors
Errors use a stable machine-readable code and human-readable message:
{
"error": {
"code": "quota_exhausted",
"message": "No full-resolution credits remain for this period.",
"requestId": "019c…"
}
}
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_input | Body, parameters, image, or dimensions are invalid |
| 401 | invalid_api_key | Credential is absent, invalid, expired, or revoked |
| 403 | insufficient_scope | Key lacks the required scope |
| 404 | job_not_found | Resource does not exist in the authenticated workspace |
| 409 | idempotency_conflict | Key was reused with different input |
| 409 | revision_conflict | Edit base is stale |
| 410 | expired_asset | Metadata exists but result bytes expired |
| 413 | image_too_large | Byte or pixel limit exceeded |
| 415 | unsupported_media | Format or signature is unsupported |
| 422 | unsafe_url | Remote URL resolves to a prohibited target |
| 428 | authorization_pending | Device flow awaits approval |
| 429 | quota_exhausted | Allowance or refinement cap exhausted |
| 500 | processing_failed | Terminal processor failure |
| 503 | processor_unavailable | No healthy processing capacity |
Retry 429 after reset. Retry idempotent 5xx requests with backoff and jitter. Fix invalid input before retrying.
SDK examples
The TypeScript and Python SDKs are generated from the same OpenAPI 3.1 document served at /openapi.json.
import { postV1BackgroundRemovals } from "@blankground/sdk";
const result = await postV1BackgroundRemovals({
body: { image: file },
headers: {
Authorization: `Bearer ${process.env.BLANKGROUND_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
Prefer: "wait=8",
},
});
import os
from background_removal_api_client import AuthenticatedClient
from background_removal_api_client.api.jobs import post_v1_background_removals
client = AuthenticatedClient(
base_url="http://127.0.0.1:3001",
token=os.environ["BLANKGROUND_API_KEY"],
)
with open("product.jpg", "rb") as image:
response = post_v1_background_removals.sync_detailed(
client=client,
body={"image": image},
idempotency_key="catalog-item-184-v1",
)
Generated method signatures track the committed OpenAPI document. Check the SDK README for the exact version’s multipart wrapper types.