API

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

September 6, 20266 min readBy BG Clear Editorial

PHP does not have a good in-process background-removal option — the Python models do not port, and shelling out to them from a web request is a support ticket waiting to happen. An HTTP call is the sane path. This tutorial shows the bgclear API from plain cURL, from Guzzle, and from Laravel's Http client, then a queued job for catalogues. The request format is remove.bg's, so if you used the popular remove.bg PHP/Laravel package, the migration is a URL and a header.

In this guide

Plain PHP with cURL

CURLFile builds the multipart part; the response body is the PNG itself.

<?php
$key = getenv('BGCLEAR_API_KEY');

$ch = curl_init('https://www.bgclear.ai/api/v1/remove');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer $key"],
    CURLOPT_POSTFIELDS     => [
        'image_file' => new CURLFile('photo.jpg', 'image/jpeg', 'photo.jpg'),
        'size'       => 'auto',      // preview (free, ≤800px) | full (1 credit) | auto
        'format'     => 'png',       // png | webp | jpg
    ],
    CURLOPT_TIMEOUT        => 60,
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    $err = json_decode($body, true)['error'] ?? ['message' => $body];
    throw new RuntimeException("bgclear $status: {$err['message']}");
}
file_put_contents('photo-no-bg.png', $body);

Add 'bg_color' => 'ffffff' and 'format' => 'jpg' for a marketplace-ready white background, or 'crop' => 'true' to trim to the subject.

Guzzle: multipart and JSON modes

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://www.bgclear.ai/api/v1/', 'timeout' => 60]);
$auth   = ['Authorization' => 'Bearer ' . getenv('BGCLEAR_API_KEY')];

// multipart upload → image bytes back
$res = $client->post('remove', [
    'headers'   => $auth,
    'multipart' => [
        ['name' => 'image_file', 'contents' => fopen('photo.jpg', 'r'), 'filename' => 'photo.jpg'],
        ['name' => 'size', 'contents' => 'auto'],
    ],
]);
file_put_contents('photo-no-bg.png', (string) $res->getBody());
echo 'credits left: ', $res->getHeaderLine('X-Credits-Remaining'), PHP_EOL;

// URL input → JSON with a hosted result URL (valid 24 h, no auth to fetch)
$res = $client->post('remove', [
    'headers' => $auth + ['Accept' => 'application/json'],
    'json'    => ['image_url' => 'https://example.com/shoe.jpg', 'size' => 'auto',
                  'format' => 'webp', 'bg_color' => 'ffffff'],
]);
$data = json_decode((string) $res->getBody(), true);
// $data['url'], $data['width'], $data['height'], $data['credits_remaining']

Guzzle throws ClientException on 4xx; read $e->getResponse()->getBody() for the JSON error with its stable code (insufficient_credits on 402, rate_limited on 429).

Laravel: Http client with attach()

Laravel's client wraps Guzzle and reads naturally. Put the key in config/services.php and .env, never in the controller.

// config/services.php
'bgclear' => ['key' => env('BGCLEAR_API_KEY')],
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

public function cutout(string $path): string
{
    $response = Http::withToken(config('services.bgclear.key'))
        ->timeout(60)
        ->attach('image_file', Storage::get($path), basename($path))
        ->post('https://www.bgclear.ai/api/v1/remove', [
            'size'   => 'auto',
            'format' => 'png',
        ]);

    if ($response->failed()) {
        $error = $response->json('error.message') ?? $response->body();
        throw new \RuntimeException("bgclear {$response->status()}: $error");
    }

    $out = 'cutouts/' . pathinfo($path, PATHINFO_FILENAME) . '.png';
    Storage::put($out, $response->body());
    return $out;
}

For an uploaded file in a controller, $request->file('image')->get() gives the contents for attach(). The idempotency header stops a double-submitted form from charging twice: ->withHeaders(['Idempotency-Key' => md5_file($tmpPath)]).

Bulk: a queued job per image

Catalogue imports belong in the queue, one job per image, with retries that cannot double-charge and a 429 that backs off instead of failing the batch.

class RemoveBackgroundJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 5;
    public function backoff(): array { return [10, 30, 60, 120, 300]; }

    public function __construct(public int $productImageId) {}

    public function handle(): void
    {
        $image = ProductImage::findOrFail($this->productImageId);

        $response = Http::withToken(config('services.bgclear.key'))
            ->withHeaders(['Idempotency-Key' => 'product-image-' . $image->id])
            ->timeout(90)
            ->attach('image_file', Storage::get($image->path), basename($image->path))
            ->post('https://www.bgclear.ai/api/v1/remove', ['size' => 'full', 'format' => 'webp', 'bg_color' => 'ffffff']);

        if ($response->status() === 429) {
            $this->release((int) $response->header('X-RateLimit-Reset', 10));
            return;
        }
        if ($response->status() === 402) {
            $this->fail(new \RuntimeException('Out of credits — top up at bgclear.ai/api-pricing/'));
            return;
        }
        $response->throw();

        Storage::put("catalogue/{$image->id}.webp", $response->body());
        $image->update(['cutout_path' => "catalogue/{$image->id}.webp"]);
    }
}

Dispatch with RemoveBackgroundJob::dispatch($id) from an Artisan command that iterates the catalogue; queue:work --max-jobs keeps memory flat. For images over 4 megapixels, post to /api/v1/jobs instead and let a callback route finish the work — see the bulk guide.

If you used the remove.bg PHP package

The widely used mtownsend/remove-bg package (and the Laravel wrapper) hard-codes api.remove.bg and the X-Api-Key header, and remove.bg's API ends on 1 December 2026. You do not need a replacement package — the Http snippet above is shorter than the fluent call was. Map the parameters one-to-one: ->size('auto')'size' => 'auto', ->format('png')'format' => 'png', ->bgColor('ffffff')'bg_color' => 'ffffff'. Drop ->type(), ->roi(), ->scale() and the zip format; they have no equivalent. The full mapping is on the migration page, pricing on API pricing.

Frequently asked questions

Is there an official PHP or Laravel package?

No, and you do not need one — Laravel's Http client or Guzzle covers the whole API in a few lines, as shown above.

Can I send the image as a URL instead of a file?

Yes — post JSON with image_url (or image_file_b64) and add Accept: application/json to get a hosted result URL back instead of bytes.

What does a full-resolution image cost?

One credit: $9 for 100, $39 for 500, $129 for 2,000. Credits never expire, previews (size=preview, ≤800 px) are free, failed requests are not charged.

How do I keep the key out of the repo?

Store it in .env as BGCLEAR_API_KEY, read it through config('services.bgclear.key'), and rotate it from the API dashboard if it ever leaks.

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 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 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

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

remove.bg Is Shutting Down on 1 December 2026: Migrate Your API Integration in 10 Minutes

remove.bg's standalone site and API close on 1 Dec 2026 (9:00 CET) as it folds into Canva's Leonardo.Ai, and unused credits expire the same day. A field-by-field migration to a drop-in compatible API, with code.