API

A Serverless Proxy for a Background Removal API on Cloudflare Workers and Vercel Edge

September 6, 20266 min readBy BG Clear Editorial

Static sites, mobile apps and browser extensions all hit the same wall: the background-removal API needs a key, and the client is the one place a key must not live. A serverless edge function is the cheapest fix — no server to run, a free tier that covers most side projects, and a global endpoint that adds a few milliseconds. This post builds the proxy on Cloudflare Workers, repeats it as a Vercel Edge route, and covers the two things that bite in production: request body limits and abuse.

In this guide

Cloudflare Worker: forward the upload, stream the result

// src/index.js — wrangler secret put BGCLEAR_API_KEY ; wrangler secret put PROXY_TOKEN
export default {
  async fetch(request, env) {
    if (request.method !== "POST") return new Response("POST only", { status: 405 });
    if (request.headers.get("X-Proxy-Token") !== env.PROXY_TOKEN) {
      return new Response("unauthorized", { status: 401 });         // your app's shared token
    }

    const inbound = await request.formData();
    const file = inbound.get("image");
    if (!(file instanceof File)) return Response.json({ error: "image missing" }, { status: 400 });
    if (file.size > 25 * 1024 * 1024) return Response.json({ error: "max 25 MB" }, { status: 413 });

    const size = new URL(request.url).searchParams.get("size") === "full" ? "full" : "preview"; // preview is free
    const form = new FormData();
    form.append("image_file", file, file.name);
    form.append("size", size);
    form.append("format", "png");

    const upstream = await fetch("https://www.bgclear.ai/api/v1/remove", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${env.BGCLEAR_API_KEY}`,
        "Idempotency-Key": `${size}-${file.size}-${file.name}`, // a client retry never double-charges
      },
      body: form,
    });

    if (!upstream.ok) return new Response(upstream.body, { status: upstream.status, headers: { "Content-Type": "application/json" } });
    return new Response(upstream.body, {                             // stream, do not buffer
      headers: {
        "Content-Type": upstream.headers.get("content-type") ?? "image/png",
        "X-Credits-Remaining": upstream.headers.get("x-credits-remaining") ?? "",
        "Cache-Control": "no-store",
      },
    });
  },
};
# wrangler.toml
name = "cutout-proxy"
main = "src/index.js"
compatibility_date = "2026-09-01"

Workers count CPU time, not time spent waiting on fetch, so a five-second removal costs almost nothing against the limit. Secrets set with wrangler secret put never appear in the bundle or the dashboard logs.

Vercel Edge route: the same proxy

// app/api/cutout/route.ts
export const runtime = "edge";

export async function POST(req: Request) {
  if (req.headers.get("x-proxy-token") !== process.env.PROXY_TOKEN) {
    return new Response("unauthorized", { status: 401 });
  }
  const inbound = await req.formData();
  const file = inbound.get("image");
  if (!(file instanceof File)) return Response.json({ error: "image missing" }, { status: 400 });

  const size = new URL(req.url).searchParams.get("size") === "full" ? "full" : "preview";
  const form = new FormData();
  form.append("image_file", file, file.name);
  form.append("size", size);
  form.append("format", "png");

  const upstream = await fetch("https://www.bgclear.ai/api/v1/remove", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.BGCLEAR_API_KEY}`, "Idempotency-Key": `${size}-${file.size}-${file.name}` },
    body: form,
  });
  return new Response(upstream.body, {
    status: upstream.status,
    headers: { "Content-Type": upstream.headers.get("content-type") ?? "application/json", "Cache-Control": "no-store" },
  });
}

Vercel's request body limit is the constraint here (4.5 MB for serverless functions; check the current limit for the Edge runtime in Vercel's docs). For phone photos that exceed it, do not forward bytes at all: upload the file to Vercel Blob, R2 or S3 from the client with a signed URL, then have the edge route send image_url in a JSON body with Accept: application/json — the API fetches the image itself and returns a hosted result URL. That pattern has no body limit and is what the Next.js guide recommends for large uploads.

Abuse control: a cooldown without a database

A public endpoint that spends your credits needs more than a shared token if the token ships in a browser bundle. On Workers, the Rate Limiting binding or a KV counter keyed by client IP is enough for a per-minute cap; on Vercel, Vercel KV or Upstash serves the same purpose. The cheapest version keeps size=preview (free at the API) for anonymous traffic and requires your own session token for size=full, so the worst an abuser can do is burn free previews.

// Workers: per-IP cooldown with the Rate Limiting binding (wrangler.toml: [[unsafe.bindings]] … type = "ratelimit")
const { success } = await env.LIMITER.limit({ key: request.headers.get("CF-Connecting-IP") ?? "anon" });
if (!success) return new Response("slow down", { status: 429, headers: { "Retry-After": "60" } });

The API itself allows 60 requests a minute per key and answers 429 with an X-RateLimit-Reset header; pass that through to the client so it backs off.

Cost and compatibility

Both platforms' free tiers comfortably cover a side project; the API cost is one credit per full-resolution image — $9 for 100, $39 for 500, $129 for 2,000 — with unlimited free previews and no subscription (pricing). The request fields are remove.bg's, so a proxy that used to front api.remove.bg changes the URL and the header before that API closes on 1 December 2026 — mapping here. For a full server rather than an edge function, see the Node and Python proxy guides.

Frequently asked questions

Why not call the API from the browser with the key in an environment variable?

Front-end environment variables are compiled into the bundle and visible to anyone. The proxy keeps the key on the edge and lets you add auth and a cooldown.

Does streaming matter?

Yes on edge runtimes with small memory limits: returning upstream.body pipes the PNG through without holding a 10 MB file in memory.

What about images larger than the platform's body limit?

Upload to object storage with a signed URL first, then send image_url to the API from the edge route; the result comes back as a hosted URL valid for 24 hours.

How much does a full-resolution image cost through the proxy?

The same one credit ($0.065–0.09 depending on pack); the proxy adds nothing. Previews 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

Cheapest Background Remover API

cheapest background remover api

Image Segmentation API

image segmentation api

Keep reading

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

Remove Image Backgrounds in Node.js with fetch and FormData (No SDK)

Node 18+ built-in fetch, a file upload, a URL input, an Express proxy endpoint so your browser never sees the key, and retries that cannot double-charge.

API

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

A FastAPI endpoint with httpx that streams the cutout back, the Django view equivalent, per-user quotas, and a Celery task for catalogue runs — the pattern behind every web or mobile front end that removes backgrounds.

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.