API

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

September 6, 20266 min readBy BG Clear Editorial

Node 18 ships fetch, FormData and Blob, which is everything a background-removal call needs — no axios, no form-data package, no SDK. This tutorial covers the direct call, a URL-based JSON call, an Express endpoint that keeps your key server-side, and the two details that make it production-safe: idempotent retries and rate-limit handling.

In this guide

Direct call: file in, PNG out

import fs from "node:fs/promises";

const API = "https://www.bgclear.ai/api/v1/remove";
const KEY = process.env.BGCLEAR_API_KEY;

export async function removeBackground(inPath, outPath, size = "full") {
  const form = new FormData();
  form.append("image_file", new Blob([await fs.readFile(inPath)]), inPath);
  form.append("size", size);          // preview (free, ≤800px) | full | auto
  form.append("format", "png");        // png | webp | jpg

  const res = await fetch(API, {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}` },
    body: form,
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${res.status} ${error.code}: ${error.message}`);
  }
  await fs.writeFile(outPath, Buffer.from(await res.arrayBuffer()));
  console.log("credits remaining:", res.headers.get("X-Credits-Remaining"));
}

await removeBackground("photo.jpg", "photo-no-bg.png");

Do not set Content-Type yourself — fetch adds the multipart boundary. The field names are the remove.bg ones, so if you are migrating from remove.bg, only the URL and the header change.

URL input with a JSON response

When the source image is already online, send JSON and ask for JSON back. The response carries a hosted result URL (valid 24 hours, no auth needed to fetch) that you can store or hand to the next service.

const res = await fetch(API, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${KEY}`,
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    image_url: "https://example.com/product.jpg",
    size: "auto",
    format: "webp",
    bg_color: "ffffff",   // omit for transparent
  }),
});
const data = await res.json();
// { id, url, width, height, credits_charged, credits_remaining, processing_ms }

An Express endpoint that hides the key

Never call the API from the browser — the key would ship in your bundle. Proxy through your server: the browser uploads to you, you forward to the API, you stream the PNG back.

import express from "express";
import multer from "multer";

const app = express();
const upload = multer({ limits: { fileSize: 25 * 1024 * 1024 } });

app.post("/api/cutout", upload.single("image"), async (req, res) => {
  const form = new FormData();
  form.append("image_file", new Blob([req.file.buffer]), req.file.originalname);
  form.append("size", "auto");

  const upstream = await fetch("https://www.bgclear.ai/api/v1/remove", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BGCLEAR_API_KEY}`,
      // one key per upload → a browser retry can never charge twice
      "Idempotency-Key": `${req.file.size}-${req.file.originalname}-${req.ip}`,
    },
    body: form,
  });

  if (!upstream.ok) return res.status(upstream.status).json(await upstream.json());
  res.type(upstream.headers.get("content-type"));
  res.send(Buffer.from(await upstream.arrayBuffer()));
});

app.listen(3000);

Add your own auth and a per-user quota in front of this route; otherwise anyone who finds it can spend your credits.

Retries and rate limits

The API allows 60 requests a minute per key and answers 429 with an X-RateLimit-Reset header (seconds). Combine that with the idempotency header for a retry loop that is safe to run in a queue worker.

async function withRetry(fn, tries = 3) {
  for (let i = 0; i < tries; i++) {
    const res = await fn();
    if (res.ok) return res;
    if (res.status === 429) {
      const wait = Number(res.headers.get("X-RateLimit-Reset") ?? 5);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    if (res.status === 402) throw new Error("Out of credits — top up at bgclear.ai/api-pricing/");
    if (res.status >= 500) continue;      // processing_failed / gpu_unavailable → retry
    throw new Error(await res.text());     // 4xx: fix the request
  }
  throw new Error("gave up after retries");
}

Large images: jobs and polling

Sync calls at full size are limited to 4 megapixels. Bigger images (up to 50 MP) go to /api/v1/jobs, which returns a job id immediately.

const JOBS = "https://www.bgclear.ai/api/v1/jobs";
const auth = { Authorization: `Bearer ${KEY}` };

const form = new FormData();
form.append("image_file", new Blob([await fs.readFile("huge.jpg")]), "huge.jpg");
form.append("size", "full");
const { job_id } = await (await fetch(JOBS, { method: "POST", headers: auth, body: form })).json();

let job;
do {
  await new Promise((r) => setTimeout(r, 2000));
  job = await (await fetch(`${JOBS}/${job_id}`, { headers: auth })).json();
} while (job.status === "queued" || job.status === "processing");

if (job.status === "done") {
  const png = await (await fetch(job.url)).arrayBuffer();   // no auth needed, 24 h
  await fs.writeFile("huge-no-bg.png", Buffer.from(png));
}

Prefer an X-Callback-Url header over polling in production; the API POSTs to it when the job finishes. Batches of up to 50 URLs go through /api/v1/jobs/batch.

Frequently asked questions

Does this work in Bun or Deno?

Yes — both implement fetch, FormData and Blob the same way; only the file-reading calls differ.

Can I call the API from the browser directly?

Technically yes, but it exposes your key. Proxy through a server route as shown, and add your own auth and quota.

Which output format should I use?

png for transparency with maximum compatibility, webp for transparency at a much smaller size, jpg with bg_color for marketplace listings that need a solid background.

How much does it cost?

Preview results (≤800 px) are free. Full-resolution results are one credit each: $9 for 100, $39 for 500, $129 for 2,000, credits never expire.

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

Remove BG API Node.js Guide

remove bg api nodejs

Background Removal API Free

background removal api free

Canva Background Remover Alternative

canva background remover alternative

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

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.

API

Automate Background Removal in n8n, Zapier and Make (No Code)

Exact node-by-node settings to remove backgrounds inside an automation: HTTP Request node in n8n, Webhooks by Zapier, and Make's HTTP module — then send the cutout to Drive, Shopify or Airtable.

API

7 remove.bg API Alternatives Compared (2026): Price per Image, Free Tier, Drop-in Compatibility

With remove.bg closing on 1 Dec 2026, here is what the alternatives actually charge per image, what you get free, and which ones let you keep your existing request code.