API

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

September 6, 20266 min readBy BG Clear Editorial

The r/FlutterDev thread asking for a background remover API is still one of Google's top results for the query, and most answers skip the part that matters: a key embedded in a mobile app is public within a day of release. This guide shows the pattern that survives — a small proxy on your backend that holds the bgclear key, plus the Flutter and React Native code that talks to it. The proxy is 30 lines; the mobile code is mostly a multipart upload.

In this guide

Why a proxy, and what it looks like

Anyone can unzip an APK or IPA and grep for bgc_live_. Once the key leaks, strangers spend your credits. So the app never talks to bgclear directly: it uploads to /cutout on your server, which forwards the file with the key and streams the PNG back. That endpoint also lets you enforce login and per-user quotas. A Node/Express version (the same idea works in any stack — the PHP and Python guides have equivalents):

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

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

app.post("/cutout", requireUser, 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", req.query.size === "full" ? "full" : "preview"); // preview is free
  form.append("format", "png");

  const r = await fetch("https://www.bgclear.ai/api/v1/remove", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BGCLEAR_API_KEY}`,
      "Idempotency-Key": `${req.user.id}-${req.file.size}-${req.file.originalname}`,
    },
    body: form,
  });
  if (!r.ok) return res.status(r.status).json(await r.json());
  res.type("image/png").send(Buffer.from(await r.arrayBuffer()));
});

Serve previews (free, ≤800 px) by default and full resolution only when the user confirms an export — that single decision keeps the credit bill proportional to real usage.

Flutter: http.MultipartRequest

import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';

Future<Uint8List> removeBackground(XFile picked, {bool full = false}) async {
  final uri = Uri.parse('https://api.yourapp.com/cutout?size=${full ? "full" : "preview"}');
  final req = http.MultipartRequest('POST', uri)
    ..headers['Authorization'] = 'Bearer $userSessionToken'   // YOUR auth, not the bgclear key
    ..files.add(await http.MultipartFile.fromPath('image', picked.path, filename: picked.name));

  final streamed = await req.send().timeout(const Duration(seconds: 60));
  final res = await http.Response.fromStream(streamed);

  if (res.statusCode != 200) {
    // The proxy passes bgclear's JSON error through: {"error": {"code": ..., "message": ...}}
    throw Exception('cutout failed ${res.statusCode}: ${res.body}');
  }
  return res.bodyBytes;   // show with Image.memory(bytes), save with image_gallery_saver etc.
}

Compress before upload with image_picker's imageQuality or flutter_image_compress; a 12 MP phone photo is 4–6 MB and the preview result is 800 px anyway. Show the preview instantly, then offer "Export HD" which re-requests with full=true.

React Native: fetch + FormData

import * as FileSystem from "expo-file-system";

export async function removeBackground(uri, { full = false } = {}) {
  const form = new FormData();
  form.append("image", { uri, name: "photo.jpg", type: "image/jpeg" });

  const res = await fetch(`https://api.yourapp.com/cutout?size=${full ? "full" : "preview"}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${sessionToken}` }, // your session, never the bgclear key
    body: form,                                           // let fetch set the multipart boundary
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${res.status} ${error?.code}: ${error?.message}`);
  }
  // Persist the PNG so <Image source={{ uri }} /> can show it
  const blob = await res.blob();
  const base64 = await new Promise((resolve) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result.split(",")[1]);
    reader.readAsDataURL(blob);
  });
  const out = `${FileSystem.cacheDirectory}cutout-${Date.now()}.png`;
  await FileSystem.writeAsStringAsync(out, base64, { encoding: FileSystem.EncodingType.Base64 });
  return out;
}

On Android, the file:// URI from the image picker works directly in FormData; on iOS, ph:// URIs need copying to a file first (expo-image-picker does this for you).

Errors, offline and cost

Surface three cases in the UI: 402 insufficient_credits from the proxy means your account is empty — that is your alert, not the user's; 413 or 400 image_too_large means compress harder (limit is 25 MB and 50 MP); a timeout means retry with the same file — the proxy's idempotency key makes that free. Queue requests when offline instead of failing; the result URL semantics do not matter here because the proxy returns bytes.

Cost is one credit per full-resolution export, from $0.065 to $0.09 depending on the pack; previews cost nothing, so a freemium app can give unlimited previews and charge for HD. Pricing. If your app used remove.bg, note its API ends on 1 December 2026; the proxy above is field-compatible — migration page.

Frequently asked questions

Can I skip the proxy for a prototype?

For a local prototype, yes — call https://www.bgclear.ai/api/v1/remove directly with the Bearer header. Never ship that build; rotate the key from the dashboard before release.

Is there an on-device option?

On-device models exist (TFLite/ONNX U²-Net variants) but cost app size, battery and quality on hair. The API pattern gives full-quality results and a 5-second round trip on mobile data.

How do I show progress?

Show the picked image immediately, overlay a spinner, and swap in the preview when it arrives (typically 2–5 s). Request full resolution only on export.

What does it cost per user?

Previews are free. A full-resolution export is one credit ($0.065–0.09). Most apps charge users for HD exports and keep previews 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

Canva Background Remover Alternative

canva background remover alternative

Cheapest Background Remover API

cheapest background remover api

Keep reading

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

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

Remove Image Backgrounds in PHP and Laravel with an API (cURL, Guzzle, Http Client)

Plain PHP with cURL, Guzzle multipart, Laravel's Http client with attach(), a queued job for bulk, and the two-line change if you used the remove.bg PHP package.

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.