Свой Telegram-бот на Claude или GPT: минимальный пример

Собираю минимального Telegram-бота на aiogram, который дергает Claude или GPT через шлюз. Полный код, плюс что делать с ошибками баланса и лимитов прямо в чате.

3 мин · · Обновлено · разработка

Ниже — минимальный, но рабочий телеграм-бот на aiogram, который пересылает сообщение пользователя в Claude или GPT через шлюз и отвечает текстом. Свой бот в Telegram — это отдельная история от бонуса за подписку на канал @zukkopro: там вы привязываете свой Telegram к аккаунту в кабинете zukko.pro и один раз получаете $0.50, а этого бота вы пишете сами и он использует ваш API-ключ для генерации ответов.

Установка и токен бота

bash
pip install aiogram httpx

Токен самого Telegram-бота получаете у @BotFather в Telegram — это отдельная сущность от fetch_... ключа шлюза, их нельзя путать: один авторизует бота в Telegram, второй — запрос к модели.

Код бота на Claude

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

TELEGRAM_TOKEN = "ваш_токен_из_botfather"
ZUKKO_API_KEY = "fetch_ваш_ключ"

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(

Тот же бот, но на GPT/GLM

Меняется только функция запроса — эндпоинт и путь до текста ответа другие:

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"Ошибка: {data.get('error', {}).get('message', 'unknown')}"
    return data["choices"][0]["message"]["content"]

Вызывайте с нужным model: gpt-5.6-sol, gpt-5.6-luna, glm-5.3, glm-5.3-flash (Claude тоже можно звать этим путём — шлюз конвертирует формат) — вся линейка идёт через один и тот же путь /v1/chat/completions; актуальный список — GET https://api.zukko.pro/v1/models.

Обработка лимитов прямо в диалоге

Если бот публичный и им пользуется больше одного человека, разумно поймать 429 отдельно и явно сказать об этом в чате, а не молчать:

bash
if resp.status_code == 429:
    code = data.get("error", {}).get("code", "")
    if code == "key_daily_limit_reached":
        return "Дневной лимит ключа исчерпан, попробуйте после 00:00 UTC"
    if code == "key_monthly_limit_reached":
        return "Месячный лимит ключа исчерпан"
    # прочие 429 приходят от провайдера модели — у самого шлюза rate limit нет
    return "Слишком много запросов, подождите немного"

Дневной и месячный лимит расхода на ключ настраивается в кабинете на странице ключа (/app/api-keys) — для публичного бота стоит поставить разумный потолок, чтобы один настойчивый пользователь не выжег весь баланс за вечер.

Идемпотентность на случай дублей от Telegram

Telegram иногда присылает одно и то же обновление дважды при нестабильном соединении с вашим сервером — если это критично (например, бот тратит деньги на длинную генерацию), добавляйте в заголовки конкретного запроса Idempotency-Key, построенный из message.message_id и chat.id, — он уникален для каждого сообщения и одинаков при его дубле, поэтому дубль не спишется повторно:

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

Генерация изображений из бота

Генерация изображений через Zukko временно недоступна — раздел оставлен для справки. Актуальный список моделей — GET https://api.zukko.pro/v1/models.

Для команды вида /image кот в скафандре дергается отдельный эндпоинт с отдельным биллингом — за штуку, не за токены:

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

Проверка баланса перед сессией

Полезная привычка для бота, который крутится без присмотра — раз в час проверять баланс и слать себе алерт при низком остатке:

bash
curl https://api.zukko.pro/v1/balance -H "Authorization: Bearer fetch_ваш_ключ"

Ключ создаётся на https://zukko.pro/app/api-keys и показывается один раз — храните в переменных окружения сервера, где крутится бот, не в коде. Стартового баланса нет; если это тестовый проект, $0.50 на первые вызовы один раз даёт подписка на @zukkopro с привязкой Telegram в кабинете.

Команда /stats для админа бота

Удобно завести скрытую команду, которая отдаёт вам историю трат — эндпоинт /v1/usage отдаёт историю по всему аккаунту (все ключи вместе, фильтра по ключу нет) и поддерживает курсорную пагинацию, так что для дайджеста за день достаточно последних записей:

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

Здоровье сервиса перед стартом polling

Если бот падает с непонятными таймаутами, а ключ и баланс в порядке, стоит сначала проверить сам сервис, а не свой код:

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

Это публичный JSON со статусом моделей, без ключа — быстрый способ отличить временную недоступность шлюза (тогда модели будут отвечать 503 с provider_unavailable) от бага в самом боте.

Реферальная ссылка для читателей бота

Если бот публичный и им пользуются подписчики канала, есть смысл рассказать им, что за подписку на @zukkopro начисляется $0.50, а по реферальной программе с их будущих пополнений капает 10% приглашавшему — это две разные механики, и в боте их стоит объяснять отдельно, чтобы не путать бонус за подписку с реферальным процентом.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.