Remove Image Backgrounds in Ruby and Rails with an API (Net::HTTP, Faraday, Active Storage)
remove.bg shipped an official Ruby gem, which is why a surprising number of Rails apps have background removal wired into Active Storage callbacks. With remove.bg's API ending on 1 December 2026, those apps need a new endpoint. The bgclear API takes the same request fields, and Ruby's standard library already speaks multipart, so this is a short one: Net::HTTP, Faraday, then a Rails job that attaches the result.
In this guide
Net::HTTP, no gems
set_form with a multipart/form-data content type builds the request correctly, including the file part.
require "net/http"
require "uri"
API = URI("https://www.bgclear.ai/api/v1/remove")
def remove_background(path, out, size: "auto")
req = Net::HTTP::Post.new(API)
req["Authorization"] = "Bearer #{ENV.fetch("BGCLEAR_API_KEY")}"
req.set_form(
[["image_file", File.open(path, "rb"), { filename: File.basename(path) }],
["size", size], # preview (free, ≤800px) | full (1 credit) | auto
["format", "png"]], # png | webp | jpg
"multipart/form-data"
)
res = Net::HTTP.start(API.host, API.port, use_ssl: true, read_timeout: 60) { |http| http.request(req) }
unless res.is_a?(Net::HTTPSuccess)
error = JSON.parse(res.body)["error"] rescue { "message" => res.body }
raise "bgclear #{res.code}: #{error["message"]}"
end
File.binwrite(out, res.body)
puts "credits left: #{res["X-Credits-Remaining"]}"
end
remove_background("photo.jpg", "photo-no-bg.png")Faraday, with JSON mode for URL inputs
require "faraday"
require "faraday/multipart"
conn = Faraday.new("https://www.bgclear.ai/api/v1") do |f|
f.request :multipart
f.request :url_encoded
f.headers["Authorization"] = "Bearer #{ENV.fetch("BGCLEAR_API_KEY")}"
f.options.timeout = 60
end
# file upload → bytes
res = conn.post("remove", image_file: Faraday::Multipart::FilePart.new("photo.jpg", "image/jpeg"),
size: "auto")
File.binwrite("photo-no-bg.png", res.body) if res.success?
# URL input → JSON with hosted result URL (24 h, no auth)
res = conn.post("remove") do |r|
r.headers["Content-Type"] = "application/json"
r.headers["Accept"] = "application/json"
r.body = { image_url: "https://example.com/shoe.jpg", size: "auto", format: "webp", bg_color: "ffffff" }.to_json
end
data = JSON.parse(res.body) # "url", "width", "height", "credits_charged", "credits_remaining"Rails: an Active Storage job that attaches the cutout
Run it from after_commit on the model, never inline in the request; the job retries with a back-off on 429 and stops cleanly on 402.
class RemoveBackgroundJob < ApplicationJob
queue_as :default
retry_on Net::ReadTimeout, wait: :polynomially_longer, attempts: 5
def perform(product)
return unless product.photo.attached?
product.photo.open do |file|
req = Net::HTTP::Post.new(URI("https://www.bgclear.ai/api/v1/remove"))
req["Authorization"] = "Bearer #{Rails.application.credentials.bgclear_api_key}"
req["Idempotency-Key"] = "product-#{product.id}-#{product.photo.checksum}" # safe retries
req.set_form([["image_file", file, { filename: product.photo.filename.to_s }],
["size", "full"], ["format", "png"]], "multipart/form-data")
res = Net::HTTP.start(req.uri.host, req.uri.port, use_ssl: true, read_timeout: 90) { |h| h.request(req) }
case res
when Net::HTTPSuccess
product.cutout.attach(io: StringIO.new(res.body), filename: "#{product.id}-cutout.png",
content_type: "image/png")
when Net::HTTPTooManyRequests
retry_job(wait: res["X-RateLimit-Reset"].to_i.seconds)
when Net::HTTPPaymentRequired
raise "bgclear: out of credits — https://www.bgclear.ai/api-pricing/"
else
raise "bgclear #{res.code}: #{res.body}"
end
end
end
endImages over 4 megapixels should go through /api/v1/jobs with an X-Callback-Url pointing at a controller action — the bulk guide covers the callback receiver.
Coming from the remove.bg gem
The gem's RemoveBg.from_file("photo.jpg", size: "auto") maps to the Net::HTTP snippet with size unchanged; format:, bg_color: and crop: keep their names; type:, roi:, scale:, position: and add_shadow: have no equivalent and are ignored. The gem's zip output (format: "zip") is the one thing to replace — request png or webp instead. Credits expire at remove.bg on 1 December 2026; the full mapping is on the remove.bg API alternative page.
Frequently asked questions
Is there a bgclear gem?
No — the API is plain multipart or JSON over HTTPS and Ruby's standard library handles it, as shown. A Faraday connection is the nicest wrapper if you prefer one.
Can I get a white background for product listings?
Yes: send bg_color: "ffffff" (any hex) and format: "jpg" for the smallest marketplace-ready file.
What is the cost?
Full-resolution results are one credit each — $9 for 100, $39 for 500, $129 for 2,000; credits never expire. Preview-size results (≤800 px) are free.
How do I test edge quality first?
Use size: "preview" on a dozen of your own images — it is free and unlimited — and compare hair and glass edges before switching to full.