API

Django and FastAPI: a Background Removal Proxy Endpoint That Keeps Your API Key Safe

September 6, 20266 min readBy BG Clear Editorial

Whatever the front end — React, Flutter, a WordPress site — the background-removal call belongs on your server, where the API key lives and where you can meter usage per user. In Python that server is usually FastAPI or Django. This guide builds the proxy endpoint in both, with httpx for the upstream call, streaming so a 10 MB PNG is not buffered twice, and a Celery task for catalogue runs with retries that cannot double-charge.

In this guide

FastAPI + httpx

import os, hashlib
import httpx
from fastapi import FastAPI, UploadFile, File, HTTPException, Depends
from fastapi.responses import StreamingResponse

app = FastAPI()
BGCLEAR = "https://www.bgclear.ai/api/v1/remove"
client = httpx.AsyncClient(timeout=httpx.Timeout(90.0))

@app.post("/cutout")
async def cutout(image: UploadFile = File(...), size: str = "preview", user=Depends(current_user)):
    if size not in ("preview", "full"):
        raise HTTPException(400, "size must be preview or full")
    data = await image.read()
    if len(data) > 25 * 1024 * 1024:
        raise HTTPException(413, "max 25 MB")
    if size == "full" and not user.can_spend_credit():
        raise HTTPException(402, "quota exceeded")

    r = await client.post(
        BGCLEAR,
        headers={
            "Authorization": f"Bearer {os.environ['BGCLEAR_API_KEY']}",
            "Idempotency-Key": f"{user.id}-{size}-{hashlib.sha256(data).hexdigest()}",
        },
        files={"image_file": (image.filename, data, image.content_type or "image/jpeg")},
        data={"size": size, "format": "png"},
    )
    if r.status_code != 200:
        # pass the API's JSON error through: {"error": {"code", "message", "docs"}}
        raise HTTPException(r.status_code, r.json().get("error", {}).get("message", "upstream error"))

    if size == "full":
        user.record_credit_spent()
    return StreamingResponse(iter([r.content]), media_type=r.headers.get("content-type", "image/png"),
                             headers={"X-Credits-Remaining": r.headers.get("X-Credits-Remaining", "")})

size=preview is free at the API (≤800 px), so let anonymous users preview and gate full behind login and a quota. The SHA-256 idempotency key means a browser retry of the same file within 24 hours returns the cached result without a second charge.

Django view

import hashlib, os, requests
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, JsonResponse
from django.views.decorators.http import require_POST

@login_required
@require_POST
def cutout(request):
    image = request.FILES.get("image")
    size = "full" if request.GET.get("size") == "full" else "preview"
    if not image:
        return JsonResponse({"error": "image missing"}, status=400)
    if image.size > 25 * 1024 * 1024:
        return JsonResponse({"error": "max 25 MB"}, status=413)

    data = image.read()
    r = requests.post(
        "https://www.bgclear.ai/api/v1/remove",
        headers={"Authorization": f"Bearer {settings.BGCLEAR_API_KEY}",
                 "Idempotency-Key": f"{request.user.pk}-{size}-{hashlib.sha256(data).hexdigest()}"},
        files={"image_file": (image.name, data, image.content_type)},
        data={"size": size, "format": "png"},
        timeout=90,
    )
    if r.status_code != 200:
        return JsonResponse(r.json(), status=r.status_code)
    resp = HttpResponse(r.content, content_type=r.headers.get("Content-Type", "image/png"))
    resp["X-Credits-Remaining"] = r.headers.get("X-Credits-Remaining", "")
    return resp

BGCLEAR_API_KEY = os.environ["BGCLEAR_API_KEY"] in settings; add a per-user daily counter (a Redis INCR with expiry is enough) before allowing size=full.

Bulk: a Celery task with safe retries

import hashlib, os, requests
from celery import shared_task

@shared_task(bind=True, max_retries=5)
def remove_background(self, path: str, out: str):
    with open(path, "rb") as f:
        data = f.read()
    r = requests.post(
        "https://www.bgclear.ai/api/v1/remove",
        headers={"Authorization": f"Bearer {os.environ['BGCLEAR_API_KEY']}",
                 "Idempotency-Key": hashlib.sha256(data).hexdigest()},
        files={"image_file": (os.path.basename(path), data)},
        data={"size": "full", "format": "webp", "bg_color": "ffffff"},
        timeout=120,
    )
    if r.status_code == 429:
        raise self.retry(countdown=int(r.headers.get("X-RateLimit-Reset", 10)))
    if r.status_code == 402:
        raise RuntimeError("out of credits — https://www.bgclear.ai/api-pricing/")
    if r.status_code >= 500:
        raise self.retry(countdown=30)
    r.raise_for_status()
    with open(out, "wb") as f:
        f.write(r.content)
    return r.headers.get("X-Credits-Remaining")

Rate limit is 60 requests a minute per key, so set the worker's rate_limit="50/m" on the task and let Celery pace it. Images above 4 megapixels go to /api/v1/jobs with an X-Callback-Url pointing at a small FastAPI/Django receiver — the bulk guide shows the receiver and the batch endpoint for 50 URLs per call.

Cost and compatibility

Previews are free and unlimited; full-resolution images are one credit — $9 for 100, $39 for 500, $129 for 2,000, no subscription, credits never expire, failures never charged (pricing). The request fields (image_file, image_url, size, format, bg_color, crop) are remove.bg's, so a Django app that used remove.bg only changes the URL and header before its API closes on 1 December 2026 — mapping here. The full Python client walkthrough, including folder scripts and async jobs, is in the Python tutorial.

Frequently asked questions

Why not call the API from the browser with CORS?

Because the key would be visible in the network tab. The proxy costs 30 lines and gives you auth, quotas and logging.

Sync or async httpx?

Async (AsyncClient) in FastAPI so a 5-second removal does not block the event loop; in Django's sync views, requests is fine, or use the async view with httpx.AsyncClient.

Should I store the result or the API's result URL?

Store the bytes (or upload them to your object storage). The API's hosted result URL is convenient but expires after 24 hours.

How much does it cost?

One credit per full-resolution image ($0.065–0.09 depending on pack); preview-size results are free.

Ship it with the bgclear API

remove.bg-compatible endpoints, credits from $9 for 100 images, no subscription. Pay, get a key, make your first call in under two minutes.

Get API credits →

Tools for this guide

Background Removal API Free

background removal api free

Background Remover Python Library

background remover python library

Cheapest Background Remover API

cheapest background remover api

Keep reading

API

Remove Image Backgrounds in Python with an API (requests, 20 Lines)

A complete Python walkthrough: single image, URL input, JSON responses, a folder script with safe retries, and async jobs for large files — no ML libraries, just requests.

API

Background Removal in Flutter and React Native Apps: the Safe API Pattern

Why the API key must never ship in the app, a 30-line proxy endpoint, then the Flutter (http + MultipartRequest) and React Native (fetch + FormData) client code with progress and error handling.

API

Next.js: Remove Image Backgrounds with a Route Handler and a React Upload Component

App Router route handler that forwards uploads to a background removal API (key stays on the server), a drop-zone client component with preview and HD export, and the streaming/limits details that bite in production.

API

Bulk Background Removal via API: Async Jobs, Batches of 50 and Webhooks

How to process a catalogue of thousands of images without babysitting a script: batch submission, callbacks instead of polling, credit accounting, and what to do when the queue is full.