2025, Oct 21 20:00
Telegram Bot API Limitations: No Chat History via get_updates and Fixing Connection Pool TimedOut Errors in Python
Why the Telegram Bot API can't fetch chat history, how nested get_updates exhausts the connection pool with TimedOut errors, and fixes via run_polling or TDLib
When building a Python Telegram Bot, a common requirement is to access earlier chat messages. It looks straightforward at first glance, but the Telegram Bot API does not expose chat history. Attempts to work around that often lead to performance issues, including connection pool exhaustion with TimedOut errors during polling.
Problem demonstration
The following handler structure shows how the issue usually appears. The code is processing an incoming update and then calls get_updates again from inside the handler, which keeps hitting Telegram servers while polling is already active.
class ChatBotRunner:
    def __init__(self) -> None:
        self.api_key = os.getenv("TELEGRAM_BOT_TOKEN")
        self.app = ApplicationBuilder().token(self.api_key).connection_pool_size(20).build()
    def start(self) -> None:
        self._wire_handlers()
        self.app.run_polling()
        log.info("BOT STARTED")
    def _wire_handlers(self):
        self.app.add_handler(MessageHandler(filters=filters.TEXT, callback=menu_handler))
async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = await load_user(update)
    log.error(user.phase)
    match user.phase:
        case Phase.intro:
            log.error(update.message.text)
            log.error(update.message)
            batch = await update._bot.get_updates()
            for item in batch:
                log.error(item)
                log.error(item.message)
                last_id = item.update_id
What’s really happening and why it fails
The bot has a connection pool size set to 20. While run_polling is already fetching updates, the handler makes another call to get_updates. That additional polling continues sending requests during the processing of previous updates. With enough concurrent interactions, this quickly depletes the available connections, which results in the TimedOut error stating that all connections in the pool are occupied and the request was not sent.
Can a bot fetch chat history?
No. The Telegram Bot API does not provide a way to retrieve historical messages from a chat. If you need message history, you have to switch to a client API based on TDLib, for example tdlib. That approach works differently from bot polling and is designed for full client access, including chat history.
Fixing the connection pool timeout
The immediate fix is to avoid nested polling. You generally do not need to call get_updates manually inside your handlers, because run_polling already manages the update flow.
class ChatBotRunner:
    def __init__(self) -> None:
        self.api_key = os.getenv("TELEGRAM_BOT_TOKEN")
        self.app = ApplicationBuilder().token(self.api_key).connection_pool_size(20).build()
    def start(self) -> None:
        self._wire_handlers()
        self.app.run_polling()
        log.info("BOT STARTED")
    def _wire_handlers(self):
        self.app.add_handler(MessageHandler(filters=filters.TEXT, callback=menu_handler))
async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = await load_user(update)
    log.error(user.phase)
    match user.phase:
        case Phase.intro:
            log.error(update.message.text)
            log.error(update.message)
            # No nested get_updates() call here
            # Handle the current update only
Why this matters
Understanding the difference between the Telegram Bot API and a client API saves time and avoids dead ends. The bot interface is event-driven and does not expose history; adding manual polling inside handlers only increases load and triggers resource exhaustion under concurrency. Clear separation of concerns leads to predictable performance and fewer operational surprises.
Takeaways
If you need past messages, the Bot API won’t provide them; use a client API such as TDLib instead. For bots, let run_polling handle updates and avoid calling get_updates inside handlers. This prevents connection pool saturation and keeps your bot responsive under load.
The article is based on a question from StackOverflow by Konstantinos and an answer by mint_tube.