Your own Telegram bot on Claude or GPT: a minimal example

Building a minimal Telegram bot with aiogram that calls Claude or GPT through the gateway. Full code, plus what to do about balance and rate-limit errors right in the chat.

3 min · · Updated · development

Below is a minimal but working Telegram bot in aiogram that forwards a user's message to Claude or GPT through the gateway and replies with text. Your own bot in Telegram is a separate story from the bonus for subscribing to the @zukkopro channel: there you link your Telegram to your zukko.pro account in the dashboard and get $0.50 once, while this bot you write yourself, and it uses your own API key to generate replies.

Setup and the bot token

bash
pip install aiogram httpx

The Telegram bot token comes from @BotFather in Telegram — a separate entity from the gateway's fetch_... key, and the two shouldn't be confused: one authorizes the bot with Telegram, the other authorizes the request to a model.

Bot code for Claude

bash
import asyncio
import httpx
from aiogram import Bot, Dispatcher
from aiogram.types import Message

TELEGRAM_TOKEN = "your_botfather_token"
ZUKKO_API_KEY = "fetch_your_key"

bot = Bot(token=TELEGRAM_TOKEN)
dp = Dispatcher()

async def ask_claude(text: str) -> str:
    async with httpx.AsyncClient(timeout=60) as client:
        resp = await client.post(

Same bot, but for GPT/GLM

Only the request function changes — a different endpoint and a different path to the reply text:

bash
async def ask_openai_compatible(text: str, model: str = "gpt-5.6-sol") -> str:
    async with httpx.AsyncClient(timeout=60) as client:
        resp = await client.post(
            "https://api.zukko.pro/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {ZUKKO_API_KEY}",
                "Content-Type": "application/json",
            },
            json={
                "model": model,
                "messages": [{"role": "user", "content": text}],
            },
        )
    data = resp.json()
    if resp.status_code != 200:
        return f"Error: {data.get('error', {}).get('message', 'unknown')}"
    return data["choices"][0]["message"]["content"]

Call it with whichever model you need: gpt-5.6-sol, gpt-5.6-luna, glm-5.3, glm-5.3-flash (Claude can be called this way too — the gateway converts the format) — the whole lineup goes through the same /v1/chat/completions path; current list: GET https://api.zukko.pro/v1/models.

Handling rate limits right in the conversation

If the bot is public and used by more than one person, it's worth catching 429 separately and saying so in the chat instead of staying silent:

bash
if resp.status_code == 429:
    code = data.get("error", {}).get("code", "")
    if code == "key_daily_limit_reached":
        return "The key's daily limit is used up, try again after 00:00 UTC"
    if code == "key_monthly_limit_reached":
        return "The key's monthly limit is used up"
    # other 429s come from the model provider — the gateway itself has no rate limit
    return "Too many requests, wait a bit"

A key's daily and monthly spending cap is set in the dashboard on the key's page (/app/api-keys) — for a public bot, set a sane ceiling so one persistent user can't burn the whole balance in an evening.

Idempotency against Telegram duplicates

Telegram sometimes resends the same update twice if your server's connection is flaky — if that matters (say, the bot spends money on a long generation), add an Idempotency-Key header to that specific request, built from message.message_id and chat.id — it's unique per message and identical for its duplicate, so the duplicate doesn't get charged again:

bash
headers["Idempotency-Key"] = f"{message.chat.id}-{message.message_id}"

Generating images from the bot

Image generation through Zukko is temporarily unavailable — this section is kept for reference. Current model list: GET https://api.zukko.pro/v1/models.

For a command like /image a cat in a spacesuit, call the separate endpoint with its own per-item billing, not token-based:

bash
async def generate_image(prompt: str) -> bytes:
    async with httpx.AsyncClient(timeout=120) as client:
        resp = await client.post(
            "https://api.zukko.pro/v1/images/generations",
            headers={"Authorization": f"Bearer {ZUKKO_API_KEY}"},
            json={
                "model": "nano-banana-2-text-to-image",
                "prompt": prompt,
            },
        )
    return resp.json()

Checking the balance before a session

A useful habit for a bot running unattended is to check the balance hourly and alert yourself when it gets low:

bash
curl https://api.zukko.pro/v1/balance -H "Authorization: Bearer fetch_your_key"

The key is created at https://zukko.pro/app/api-keys and shown once — keep it in the environment variables of the server running the bot, not in code. There's no starting balance; for a test project, subscribing to @zukkopro and linking Telegram in the dashboard gives a one-time $0.50 for your first calls.

An admin-only /stats command

It's handy to add a hidden command that returns spend history — the /v1/usage endpoint returns history for the whole account (all keys together, no per-key filter) and supports cursor pagination, so the latest entries are enough for a daily digest:

bash
@dp.message(Command("stats"))
async def stats(message: Message):
    if message.from_user.id != ADMIN_ID:
        return
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.zukko.pro/v1/usage",
            headers={"Authorization": f"Bearer {ZUKKO_API_KEY}"},
            params={"limit": 20},
        )
    await message.answer(str(resp.json()))

Checking service health before starting polling

If the bot fails with unclear timeouts while the key and balance look fine, check the service itself before your own code:

bash
curl https://api.zukko.pro/api/status

This is a public JSON with model status, no key needed — a quick way to tell a temporary gateway outage (in which case models answer with 503 and provider_unavailable) apart from a bug in the bot.

If the bot is public and used by channel subscribers, it's worth telling them that subscribing to @zukkopro earns $0.50, and that the referral program pays 10% of their future top-ups to whoever invited them — these are two different mechanics, and the bot should explain them separately so the subscription bonus doesn't get confused with the referral percentage.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

Telegram bot on Claude or GPT: code and setup · Zukko