Build a Telegram Bot That Removes Image Backgrounds (python-telegram-bot + API)
Telegram bots are the quickest way to put a background remover in front of people who will never visit a website — WhatsApp-first sellers, students, small teams. The whole bot is one Python file: python-telegram-bot handles the messaging, an HTTP call does the removal, and long polling means it runs from any laptop or $5 VPS without a public URL. This guide builds it with the bgclear API, keeps previews free, and charges credits only when a user asks for HD.
In this guide
Setup
Create the bot with @BotFather in Telegram and copy the token. Get a bgclear key from the API dashboard (10 free full-resolution credits; preview-size results are free without limit).
pip install "python-telegram-bot>=20" httpx
export TELEGRAM_BOT_TOKEN=123456:ABC...
export BGCLEAR_API_KEY=bgc_live_...The bot
Two Telegram details shape the code. Photos sent as photos are recompressed to JPEG by Telegram, so the bot accepts both photos and image documents, and it always replies with a document — that is the only way the transparent PNG survives. And the largest photo size is the last element of message.photo.
import os, time, logging
import httpx
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes, filters
API = "https://www.bgclear.ai/api/v1/remove"
KEY = os.environ["BGCLEAR_API_KEY"]
COOLDOWN = 10 # seconds between requests per user
last_seen: dict[int, float] = {}
logging.basicConfig(level=logging.INFO)
async def remove_background(data: bytes, size: str) -> tuple[bytes | None, str]:
async with httpx.AsyncClient(timeout=90) as client:
r = await client.post(
API,
headers={"Authorization": f"Bearer {KEY}"},
files={"image_file": ("photo.jpg", data, "image/jpeg")},
data={"size": size, "format": "png"},
)
if r.status_code == 200:
return r.content, r.headers.get("X-Credits-Remaining", "?")
err = r.json().get("error", {})
return None, f"{r.status_code} {err.get('code')}: {err.get('message')}"
async def handle_image(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
msg = update.message
now = time.time()
if now - last_seen.get(msg.from_user.id, 0) < COOLDOWN:
await msg.reply_text("Easy — one image every 10 seconds.")
return
last_seen[msg.from_user.id] = now
tg_file = await (msg.document or msg.photo[-1]).get_file() # document keeps full quality
data = bytes(await tg_file.download_as_bytearray())
size = "full" if context.user_data.get("hd") else "preview" # preview is free
await msg.reply_chat_action("upload_document")
png, info = await remove_background(data, size)
if png is None:
await msg.reply_text(f"Could not process that image ({info}).")
return
await msg.reply_document(document=png, filename="cutout.png",
caption=f"Background removed ({size}). Send /hd for full resolution.")
context.user_data["hd"] = False # HD is per request
async def hd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
context.user_data["hd"] = True
await update.message.reply_text("Next image will be processed at full resolution (1 credit).")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Send me a photo and I will remove the background. "
"Send it as a file for best quality. /hd before an image for full resolution.")
app = Application.builder().token(os.environ["TELEGRAM_BOT_TOKEN"]).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("hd", hd))
app.add_handler(MessageHandler(filters.PHOTO | filters.Document.IMAGE, handle_image))
app.run_polling()Run it with python bot.py. Long polling pulls updates from Telegram, so the bot works behind NAT, on a laptop, or in a container with no inbound port.
Keeping it cheap and abuse-proof
Previews (≤800 px) are free at the API, so the default path costs nothing no matter how many people use the bot. /hd spends one credit — gate it behind an allow-list of user ids, a Telegram Stars payment, or a daily quota per user if the bot is public. The per-user cooldown above is in memory; a Redis counter keyed by user id survives restarts. Rate limit at the API is 60 requests a minute per key; if the bot grows past that, queue requests with a semaphore of 4–5 concurrent calls.
Telegram's own limits: bots can download files up to 20 MB, and image documents over about 10 MB are best downscaled before sending to the API. Log X-Credits-Remaining (returned with every response) and alert yourself below a threshold so the bot never dies with a 402 in front of users.
Deploying and cost
Any always-on Python host works — a small VPS, Fly.io, Railway, a Raspberry Pi — because polling needs no public URL. Store the two tokens as environment variables, never in the file. API cost is one credit per HD image: $9 for 100, $39 for 500, $129 for 2,000, no subscription, previews free, failed requests never charged (pricing). The same handler shape works for a Discord bot; for WhatsApp-style use cases in India, the Hindi WhatsApp DP guide covers the web tool.
Frequently asked questions
Why does the bot reply with a file instead of a photo?
Telegram converts photos to JPEG, which cannot hold transparency. Sending the PNG as a document preserves the transparent background.
Do I need a server with a public URL?
No — the bot uses long polling, so it runs anywhere with outbound internet. Webhooks are optional and only matter at very high volume.
Can users send images as files?
Yes, and they should for best quality: the handler accepts both photos and image documents.
What does each image cost?
Previews are free. Full-resolution (/hd) images are one credit — $0.065 to $0.09 depending on pack, credits never expire.