Guides12 min readPublished 2026-08-03

How to Build a Telegram AI Bot in Python with LumeAPI

Build a Telegram AI bot in Python with BotFather, long polling, an OpenAI-compatible model call, update deduplication, privacy and rate controls.

By LumeAPI Engineering Team

Cheap LLM API hub → Build with a Chatbot API →

Short path: Chatbot API · Multi-model API · OpenAI-compatible API · Python API guide

Last verified: August 3, 2026

A Telegram AI bot needs two server-side connections: Telegram's Bot API receives and sends messages, while an OpenAI-compatible API generates the answer. The user's phone never receives either secret. This tutorial uses long polling for a simple local deployment and LumeAPI for the model call.

The first version intentionally handles text only. Files, voice, memory and tools should be added after authentication, duplicate handling and cost limits work.

Create the bot and keep both tokens private

Message @BotFather in Telegram, run /newbot and follow the prompts. Store the returned bot token like a password; anyone who has it controls the bot.

Create environment variables on the machine that will run the code:

text
TELEGRAM_BOT_TOKEN=replace_me
LUMEAPI_API_KEY=replace_me

The LumeAPI key must be an inference key. Do not use the Research article-ingest key.

Install the Python dependencies

bash
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install openai requests

Run a minimal polling bot

Save this as bot.py:

python
import os
import time
from typing import Any

import requests
from openai import OpenAI

TELEGRAM_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
LUMEAPI_KEY = os.environ["LUMEAPI_API_KEY"]
TELEGRAM_API = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}"

ai = OpenAI(
    api_key=LUMEAPI_KEY,
    base_url="https://api.lumeapi.site/v1",
    timeout=45.0,
    max_retries=2,
)


def telegram(method: str, payload: dict[str, Any]) -> dict[str, Any]:
    response = requests.post(
        f"{TELEGRAM_API}/{method}",
        json=payload,
        timeout=40,
    )
    response.raise_for_status()
    data = response.json()
    if not data.get("ok"):
        raise RuntimeError(f"Telegram rejected {method}: {data}")
    return data


def answer_question(question: str) -> str:
    result = ai.chat.completions.create(
        model="gpt-5.4-mini",
        messages=[
            {
                "role": "system",
                "content": "Answer helpfully and concisely. Do not invent private context.",
            },
            {"role": "user", "content": question[:4000]},
        ],
        temperature=0.2,
    )
    answer = result.choices[0].message.content
    if not answer:
        raise RuntimeError("The model returned no text.")
    return answer.strip()


def run() -> None:
    offset: int | None = None

    while True:
        try:
            payload: dict[str, Any] = {
                "timeout": 30,
                "allowed_updates": ["message"],
            }
            if offset is not None:
                payload["offset"] = offset

            response = requests.post(
                f"{TELEGRAM_API}/getUpdates",
                json=payload,
                timeout=40,
            )
            response.raise_for_status()
            updates = response.json().get("result", [])

            for update in updates:
                offset = update["update_id"] + 1
                message = update.get("message", {})
                text = message.get("text")
                chat_id = message.get("chat", {}).get("id")

                if not text or chat_id is None:
                    continue

                if text == "/start":
                    telegram("sendMessage", {
                        "chat_id": chat_id,
                        "text": "Send a question and I will answer it.",
                    })
                    continue

                try:
                    reply = answer_question(text)
                    telegram("sendMessage", {
                        "chat_id": chat_id,
                        "text": reply[:4000],
                    })
                except Exception as exc:
                    print("message failed", type(exc).__name__)
                    telegram("sendMessage", {
                        "chat_id": chat_id,
                        "text": "The AI request failed. Please try again later.",
                    })

        except requests.RequestException as exc:
            print("poll failed", type(exc).__name__)
            time.sleep(3)


if __name__ == "__main__":
    run()

Start the bot:

bash
python bot.py

Open the bot in Telegram, send /start, then ask a short question.

Why offset matters

Telegram assigns every update an update_id. Sending the next expected ID as offset confirms earlier updates and prevents the same message from being returned forever during one process run.

The example keeps offset in memory. For production, persist the last acknowledged update ID or store processed message IDs. Decide whether to acknowledge before or after the model call based on your failure policy:

  • Acknowledge first: avoids duplicate model charges but may drop a reply after a crash.
  • Acknowledge after: enables recovery but can repeat the model call and message.

A durable job table with a unique update ID gives the best result: receive once, process with bounded retries and send once.

Long polling versus webhooks

Long polling is simplest for local development and one worker. A webhook is better when you already operate a public HTTPS service or need horizontal scaling. Telegram documents both through the same Bot API.

Do not run polling and a webhook for the same bot simultaneously. Also avoid starting two polling processes with one token; they can compete for updates.

Add user and cost controls before sharing the bot

The public bot username is discoverable. Before inviting users:

  1. Allowlist user or chat IDs for a private tool.
  2. Add per-user and global rate limits.
  3. Limit prompt size and generated output.
  4. Keep a model allowlist.
  5. Do not log full messages by default.
  6. Define conversation retention and deletion behavior.
  7. Moderate input and output for the community.
  8. Require explicit approval before any external tool action.

Telegram bots cannot initiate a conversation with a user; the user must contact or add the bot first. In groups, Privacy Mode affects which messages a bot can see. Keep it enabled unless the use case genuinely requires broader access.

Handle Telegram and model rate limits separately

Telegram and the model gateway have independent limits. A 429 response can include retry guidance. Do not place an unlimited while True retry inside each message handler. Use a queue, a maximum attempt count and jitter.

If the bot becomes busy, bound concurrent model calls and tell the user the request is queued. One user should not be able to consume the entire API budget by sending many messages at once.

Useful next steps

  • Add short, consented conversation memory keyed by chat ID.
  • Add retrieval from approved support documents.
  • Route simple and difficult questions to different models through the multi-model API.
  • Split long replies at paragraph boundaries instead of silently truncating.
  • Store request ID, latency, tokens and outcome for cost-per-resolved-question reporting.
  • Move from polling to a verified HTTPS webhook when deployment requires it.

Sources and verification boundary

The Python code was syntax-checked on August 3, 2026. End-to-end execution requires a Telegram bot token and a LumeAPI inference key, so duplicate behavior, privacy settings and real billing must be tested in your own bot before public use.

Ready to call these models?

Create a LumeAPI key in under a minute — one OpenAI-compatible gateway for GPT, Claude, Gemini, and more.