Telegram Bot API vs MTProto: Methods Available to Bots
Which MTProto methods can Telegram bots call? A production-tested Bot API comparison with bot-token methods, user-only limits, and Telethon examples.

Quick answer
A BotFather token is not limited to the HTTP Bot API. The same token can authorize an MTProto session, giving a bot access to native Telegram methods for participant enumeration, profile enrichment, and explicit message retrieval by ID.
I ran into this boundary while building OHLDBot. The HTTP Bot API handled normal bot interactions, but it did not expose the participant and profile workflow I needed. A bot-token MTProto session did.
The extra surface is precise and limited. Many tempting methods still return BOT_METHOD_INVALID. This guide separates methods verified in production, methods tested in a live probe, methods confirmed only by Telegram's documentation, and methods that remain user-only.
Bot API and MTProto are different interfaces
The HTTP Bot API is the standard web interface. Webhooks, getUpdates, sendMessage, getChat, file IDs, keyboards, and payments. It is the right abstraction for chat-oriented bots.
MTProto is Telegram's native client protocol. A bot can authenticate to it using its BotFather token:
from telethon import TelegramClient
client = TelegramClient("bot-session", API_ID, API_HASH)
await client.start(bot_token=BOT_TOKEN)
me = await client.get_me()
assert me.bot
Telethon wraps auth.importBotAuthorization here. You still need an api_id and api_hash in addition to the BotFather token. The token alone does not give you an MTProto session.
This does not turn the bot into a user account. Telegram's type system marks every TL method as bot or user. Calling a user-only method from a bot session returns BOT_METHOD_INVALID.
The method matrix
| Capability | HTTP Bot API | Bot over MTProto | User over MTProto | Evidence |
|---|---|---|---|---|
| Authenticate bot via MTProto | Not applicable | auth.importBotAuthorization | Not applicable | Current production code |
| Resolve exact public username | Partial (chat usernames in some endpoints) | contacts.resolveUsername | Yes | Official docs + code |
| Enumerate participants | No general endpoint | channels.getParticipants (admin access may be required) | Yes | Current production probe |
| Expanded user profile | No getFullUser equivalent | users.getFullUser with valid access_hash | Yes | Current production code |
| Expanded channel profile | getChat (bot-oriented view) | channels.getFullChannel | Yes | Official docs |
| Fetch known message by ID | No general getMessage endpoint | messages.getMessages / channels.getMessages | Yes | Production probe verified |
| Bot-visible update sync | getUpdates, webhooks | updates.getState, updates.getDifference | Yes | Official docs + earlier POC |
| Unrestricted history | Not available | Not available | messages.getHistory | Official flag: user-only |
| Dialog list | Not available | Not available | messages.getDialogs | Official flag: user-only |
| Server-side message search | Not available | Not available | messages.search | Official flag: user-only |
| Contact search / phone resolve | Not available | Not available | contacts.search, contacts.resolvePhone | Official flag: user-only |
Evidence label key: "Current production code" means the method is called by a live bot MTProto session. "Production probe" means a live session exercised the method and confirmed no error. "Official docs" means Telegram's core.telegram.org marks it bot-callable. "Earlier POC" means the method worked in a controlled experiment.
What opens up through bot-token MTProto
1. Resolve an exact public username
contacts.resolveUsername is officially bot-callable. It returns a ResolvedPeer with the peer's access_hash, type, and additional peer information.
from telethon import functions
result = await client(
functions.contacts.ResolveUsernameRequest("some_public_username")
)
# result.peer, result.chats, result.users
This is exact resolution only, with no substring search. The HTTP Bot API has no equivalent that returns a peer with an MTProto access hash.
2. Enumerate participants where the bot has access
channels.getParticipants is officially bot-callable. In practice, Telegram may return CHAT_ADMIN_REQUIRED depending on the channel's privacy settings and the bot's admin rights.
users = await client.get_participants(channel, search="", limit=100)
Telethon maps get_participants to channels.getParticipants for channels. Each returned user carries a session-scoped access_hash.
Limits: This is not a tool for enumerating arbitrary broadcast-channel subscribers. The bot must have legitimate access (typically as an admin), and Telegram enforces rate limits with FLOOD_WAIT.
3. Enrich known users with FullUser
users.getFullUser is officially bot-callable when you provide a valid InputUser containing both user_id and the access_hash observed by the same MTProto session.
from telethon import functions, types
user = types.InputUser(user_id=user_id, access_hash=access_hash)
full = await client(functions.users.GetFullUserRequest(user))
The HTTP Bot API has getChatMember, but no method that returns the expanded UserFull object available through MTProto, including the user's about text and other extended fields.
This is the pipeline that matters for practical use:
channels.getParticipants
↓
user_id + access_hash
↓
users.getFullUser
The hash is scoped to the bot session that obtained it. Reusing an access_hash from a different session or account will fail.
4. Fetch a known message by ID
messages.getHistory is user-only. But if you already know a message ID, messages.getMessages and channels.getMessages are bot-callable.
Telethon exposes a convenient helper:
message = await client.get_messages(peer, ids=message_id)
Under the hood, Telethon routes the request based on the peer type:
- Channel or supergroup →
channels.getMessages - Other applicable peer types →
messages.getMessages
You can also call the raw method explicitly:
from telethon import functions, types
result = await client(
functions.channels.GetMessagesRequest(
channel=channel,
id=[types.InputMessageID(id=message_id)],
)
)
There is no TL method called getMessageById. That name exists in library helpers and blog posts, but not in Telegram's type language.
Important:
client.iter_messages(peer)andclient.get_messages(peer)withoutids=do not use this bot-callable path. Telethon routes them tomessages.getHistoryormessages.search, which are user-only. For a bot session, explicit IDs are the difference between a valid request andBOT_METHOD_INVALID.
5. Backfill a bounded ID range
Because messages.getMessages and channels.getMessages accept lists of IDs, you can backfill a known bounded range in a channel or supergroup. Do not use this pattern for basic groups or private chats, where IDs do not form a per-peer sequence:
import asyncio
from telethon.errors import FloodWaitError
async def fetch_known_range(client, channel, first_id, last_id, batch_size=100):
"""For channels and supergroups with a known bounded ID range."""
cursor = first_id
while cursor <= last_id:
ids = list(range(cursor, min(cursor + batch_size, last_id + 1)))
try:
messages = await client.get_messages(channel, ids=ids)
except FloodWaitError as error:
await asyncio.sleep(error.seconds)
continue
for message in messages:
if message is not None:
yield message
cursor += batch_size
await asyncio.sleep(0.25)
This is not getHistory under another name. Four limits apply:
- You need a known anchor or bounded ID range. You cannot discover the latest ID without an update or a known reference point.
- IDs are not guaranteed to form a gap-free archive. Deleted posts, inaccessible messages, and
MessageEmptyresults create holes. - The bot can only retrieve messages Telegram considers visible to that bot.
- Large scans need persistence, deduplication, pacing, and
FLOOD_WAIThandling.
For a durable inbox, updates.getDifference is usually the better starting point. It recovers bot-visible updates and peers after login without pretending to be unrestricted history.
6. Recover bot-visible update state
updates.getState and updates.getDifference are officially bot-callable. They let a bot session recover the update stream it would have received through normal MTProto updates, bounded by the usual constraints.
Methods that stay blocked
These methods are user-only in Telegram's current documentation. A bot session calling them will receive BOT_METHOD_INVALID.
| Method | What it would provide | Bot-token result |
|---|---|---|
messages.getHistory | Dialog history | Blocked |
messages.getDialogs | List of dialogs | Blocked |
messages.getPeerDialogs | Individual dialog state | Blocked |
messages.search | Server-side message search | Blocked |
contacts.search | Substring search over contacts and public peers | Blocked |
contacts.resolvePhone | Resolve a phone number | Blocked |
bots.getBotRecommendations | Telegram's similar-bot suggestions | Blocked (despite its namespace) |
Bot policy also still applies. A bot cannot:
- Initiate arbitrary private chats with users who have not interacted with it
- Bypass group or channel privacy settings
- Enumerate every subscriber of a broadcast channel
- Reuse an
access_hashfrom an unrelated account or MTProto session
Where bot-token MTProto is useful
The usable surface is narrower than a user account, but it opens real engineering use cases:
| Use case | What it replaces | Risk reduction |
|---|---|---|
| Member discovery in chats the bot can access | A user account reading getParticipants | No SIM card or automated personal account |
| Richer profiles for observed users | A user account calling getFullUser per profile | Same bot identity, session-scoped hashes |
| Exact public username resolution | User-level MTProto session or separate resolver | No separate authentication needed |
| Message re-fetch over bounded ID ranges | Full history access | Only messages the bot can already see |
| Bot-visible update recovery | A second Bot API update loop | Stateful native update recovery |
For these jobs, a bot MTProto session can remove the need for a farm of user accounts and SIM cards, along with the operational risk that comes with automating personal Telegram accounts.
Related Telegram automation
The method boundary matters most when choosing an account model. My guide to getting a Telegram channel subscribers list in Python covers the user-session route. Watching Telegram Stories from Python is another example of a feature that needs user-level MTProto. More practical notes live in the Telegram automation topic, with broader operator notes in the English blog.
Decision guide
| Your goal | What to use |
|---|---|
| Build a regular chat bot (send messages, keyboards, payments) | HTTP Bot API: simpler, well-documented, webhooks |
| Enumerate members in a channel you admin, enrich user profiles, resolve usernames | Bot-token MTProto: you need auth.importBotAuthorization + Telethon |
| Read unrestricted history, search messages, list all dialogs | Bot over MTProto blocked: you need a user MTProto session |
| Backfill specific message IDs or ranges | Bot-token MTProto: channels.getMessages / messages.getMessages with known IDs |
| Maintain state in a native MTProto update loop | Bot-token MTProto: updates.getDifference works for bot-visible events |
Bot-token MTProto gives you a small, useful subset of Telegram's native API. It is enough for participant discovery, profile enrichment, exact username resolution, and message re-fetch by ID. It is not a hidden user mode.
Frequently asked questions
Can a Telegram bot use MTProto?
Yes. A bot can authorize an MTProto session through auth.importBotAuthorization using a BotFather token plus a Telegram api_id and api_hash. The session still has bot permissions, so user-only methods remain blocked.
Can a bot read Telegram chat history through MTProto?
No. messages.getHistory is user-only. A bot can fetch specific visible messages when it already knows their IDs, using messages.getMessages or channels.getMessages, but it cannot paginate unrestricted history.
Can a bot list channel or group members?
A bot can call channels.getParticipants for channels and supergroups it can legitimately access. Telegram may require admin rights, and broadcast-channel subscriber lists remain restricted.
Is there a Telegram MTProto method called getMessageById?
No. The underlying methods are messages.getMessages and channels.getMessages. Telethon exposes client.get_messages(peer, ids=...) as a helper and chooses the correct method for the peer type.
Does a Telegram access_hash work across accounts?
No. Treat each access_hash as session-contextual. Store it with the bot or user session that observed it and do not reuse it from an unrelated account.