TelegramAccount: automate a Telegram user account in Python over MTProto
TelegramAccount is an async Python client that acts as a regular Telegram user over MTProto, built on Telethon. It covers what the Bot API cannot: reading chat history, listing group members, joining groups and channels on its own, creating channels, posting stories and scheduling messages. You log in once with a phone number, the login code and, if enabled, the 2FA password, then reuse the session string.
from socialift import TelegramAccount90 methodsas of socialift 1.3.0Updated:
How do I set up TelegramAccount and log in?
First get an api_id and api_hash: log in to my.telegram.org with your Telegram number, open API development tools and fill in the form. Per Telegram's rules, each phone number can currently have only one api_id. Keep the keys out of your code, in the TELEGRAM_API_ID and TELEGRAM_API_HASH environment variables.
Install with pip install "socialift[telegram]" — Telethon comes in as an optional extra (Python 3.11+). The license key is read from SOCIALIFT_LICENSE_KEY or the license_key= argument and checked on connect(). async with TelegramAccount(...) as tg: opens the connection and closes it when the block exits.
Login takes three steps: send_code() → sign_in() → sign_in_password() if the account has a 2FA password. Store the session string from the result; next time TelegramAccount.from_env() reads it from TELEGRAM_SESSION and no code is needed. If the steps run in separate HTTP requests, also pass the intermediate session returned by send_code() to the next step: the phone_code_hash is only valid inside that session.
Treat the session string like a password: it holds the authorization key and grants full account access. Store it encrypted, never log it and never send it to a browser. If it leaks, call log_out() or terminate the session from another device. To try methods without code, connect the account under Ulanishlar (Connections) in the dashboard and call them from the Playground.
import asyncio
import os
from socialift import TelegramAccount
PHONE = "+14155550123"
async def login() -> str:
# Once: phone -> code -> (if enabled) 2FA password
async with TelegramAccount(
api_id=os.environ["TELEGRAM_API_ID"],
api_hash=os.environ["TELEGRAM_API_HASH"],
) as tg:
sent = await tg.send_code(PHONE)
code = input("Code from Telegram: ")
result = await tg.sign_in(PHONE, code, phone_code_hash=sent["phone_code_hash"])
if result["two_factor_required"]:
result = await tg.sign_in_password(input("2FA password: "))
return result["session"] # store encrypted -> TELEGRAM_SESSION
async def main() -> None:
# Afterwards: TELEGRAM_API_ID, TELEGRAM_API_HASH, TELEGRAM_SESSION
async with TelegramAccount.from_env() as tg:
me = await tg.me()
await tg.send_message("me", f"Hello, {me.first_name}!")
asyncio.run(main()) # first run: print(asyncio.run(login()))Connection & login
Create the client, log in with phone, code and 2FA password, export the session string, manage active sessions.
closeconnectdisconnectexport_sessionfrom_envis_authorizedlog_outreset_sessionsend_codesessionssign_insign_in_password
close
asyncAlias of disconnect(), matching the close() name used by the other SDK clients.
await tg.close() -> Noneconnect
asyncOpens the MTProto connection and checks the license, but does not check account authorization — the login flow needs a connection before it is authorized. async with TelegramAccount(...) calls it for you.
await tg.connect() -> TelegramAccount- Most methods connect on their own, but some (
raw,export_session,join,create_channel,stats,contacts,resolve) expect an open connection — useasync withor callconnect()first. - Calling it on a connected client is safe and does nothing.
disconnect
asyncCloses the connection; called automatically when an async with block exits. The Telegram session stays valid, so the stored session string keeps working.
await tg.disconnect() -> None- To also terminate the session on Telegram's side, use
log_out().
Returns the current session as a StringSession string so you can reconnect later with TelegramAccount(session=...) without a code. Synchronous, but needs an open connection.
tg.export_session() -> str- The string holds the data-center address and the authorization key, i.e. full account access. Store it encrypted and never log it.
- If it leaks:
log_out(), orreset_session()from another session.
from_env
classmethodBuilds the client from environment variables: TELEGRAM_API_ID, TELEGRAM_API_HASH and the optional TELEGRAM_SESSION. Extra keyword arguments (license_key, proxy, device_model ...) go to the constructor.
TelegramAccount.from_env(**kwargs) -> TelegramAccount- With an empty
TELEGRAM_SESSIONthe client has no session, so you must log in first (send_code→sign_in). - A missing or non-positive
TELEGRAM_API_ID, or an emptyTELEGRAM_API_HASH, raisesConfigurationError.
log_out
asyncTerminates the session on Telegram (auth.logOut) and closes the connection. The stored session string stops working; reconnecting needs the phone number and a code again.
await tg.log_out() -> bool- To just close the connection and keep the session, use
disconnect().
await tg.log_out()reset_session
asyncTerminates a session on another device by its hash from sessions() (account.resetAuthorization).
await tg.reset_session(session_id: int) -> Any- Telegram refuses to log out other sessions if the current one logged in less than 24 hours ago (
FRESH_RESET_AUTHORISATION_FORBIDDEN).
await tg.reset_session(session_hash)send_code
asyncLogin step 1: requests a login code for the phone number and returns the phone_code_hash plus an intermediate session string. Pass the number in international format; spaces and dashes are stripped for you.
await tg.send_code(phone: str) -> dict- The code may arrive in a Telegram app where the account is already logged in rather than by SMS; the
typefield tells you where it went. - The
phone_code_hashis only valid inside that session: if the steps run in separate requests, continue withTelegramAccount(..., session=sent["session"]). - If the session is already authorized, no code is sent and
already_authorized: Trueis returned.
sent = await tg.send_code("+998901234567")sessions
asyncLists every active session (device) on the account via account.getAuthorizations. Each entry has the device, app, activity time and a hash.
await tg.sessions() -> Any- The
hashidentifies the session forreset_session().
sessions = await tg.sessions()sign_in
asyncLogin step 2: signs in with the received code and phone_code_hash. On success it returns authorized: True, the permanent session string and user (id, username, phone, name).
await tg.sign_in(
phone: str,
code: str,
*,
phone_code_hash: Optional[str] = None,
) -> dict- If the account has a 2FA password, no exception is raised; you get
two_factor_required: Trueand callsign_in_password()next. - A wrong or expired code, an invalid phone number or a bad
api_id/api_hashpair raisesAuthErrorwith a readable message.
result = await tg.sign_in("+998901234567", code="12345", phone_code_hash=sent["phone_code_hash"])sign_in_password
asyncLogin step 3: completes sign-in with the two-factor (2FA) password and returns the same result shape as sign_in().
await tg.sign_in_password(password: str) -> dict- The password is never sent in plain text: Telegram uses the SRP protocol and Telethon computes the proof locally.
- A wrong password raises
AuthError. Do not log or store the password.
result = await tg.sign_in_password("mening-parolim")Profile & account
Current account, name and bio, username, profile photos.
delete_profile_photosmeprofile_photosset_profile_photoset_usernameupdate_profile
Deletes profile photos; without photos it deletes every profile photo on the account.
await tg.delete_profile_photos(photos: Optional[Sequence[Any]] = None) -> Any- To delete a single photo, pass its object from
profile_photos()in a list — calling without arguments removes them all.
me
asyncThe current account as a User: id, name, username, phone. Useful to confirm whose session you hold.
await tg.me() -> Anyme = await tg.me()profile_photos
asyncProfile photos of a user, group or channel; defaults to your own, up to 20.
await tg.profile_photos(entity: Union[int, str, Any] = 'me', *, limit: int = 20) -> listset_profile_photo
asyncUploads and sets a new profile photo from a file path, bytes or a file object.
await tg.set_profile_photo(file: Any) -> Anyset_username
asyncSets the account's public @username; an empty string removes it.
await tg.set_username(username: str) -> Any- Usernames are 5–32 characters: a–z, 0–9 and underscores.
- A taken name gives
USERNAME_OCCUPIED, a bad formatUSERNAME_INVALID; both surface asApiError.
update_profile
asyncChanges first name, last name and bio (about); fields you omit stay unchanged (account.updateProfile).
await tg.update_profile(
*,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
about: Optional[str] = None,
) -> Any- Bio length is limited, with separate limits for regular and Premium accounts (
about_length_limit_*in the client config).
Dialogs & messages
Chat list, history, sending, editing, deleting, forwarding, pinning, reactions, polls and scheduled messages.
actionarchivedelete_dialogdelete_messagesdialogsedit_messageforward_messagesmark_readmessagemessagesmutepin_dialogpin_messagereactscheduledsend_messagesend_pollunmuteunpin_message
action
asyncShows a chat status such as "typing..." or "sending photo...". Values: typing, cancel, photo, video, record-video, audio, record-audio, document, location, contact, game.
await tg.action(entity: Union[int, str, Any], action: str = 'typing') -> Any- The status lasts 6 seconds; repeat the call or use
async with tg.client.action(entity, "typing"):to keep it visible longer.
await tg.action("@durov", "typing")archive
asyncMoves a dialog to the archive (archived=True) or back out of it (archived=False).
await tg.archive(entity: Union[int, str, Any], *, archived: bool = True) -> Anydelete_dialog
asyncRemoves a dialog: leaves a channel or supergroup, leaves a basic group and clears its history, or deletes a private chat's history.
await tg.delete_dialog(entity: Union[int, str, Any], *, revoke: bool = False) -> Anyrevoke=Truedeletes a private chat's history for the other person too; it has no effect on channels and supergroups.
delete_messages
asyncDeletes messages by ID. revoke=True (default) deletes them for everyone; False only for you, in private chats and basic groups.
await tg.delete_messages(
entity: Union[int, str, Any],
message_ids: Iterable[int],
*,
revoke: bool = True,
) -> Any- In supergroups and channels messages are always deleted for everyone; deleting other people's messages needs the
delete_messagesadmin right. - Service messages cannot be deleted (
MESSAGE_DELETE_FORBIDDEN→PermissionError). - Deletion can also be time-limited (
revoke_time_limit,revoke_pm_time_limit).
await tg.delete_messages("me", [101, 102], revoke=True)dialogs
asyncLists dialogs — private chats, groups and channels — by recent activity. Defaults to 50; archived=True returns only the archive, folder selects a folder ID.
await tg.dialogs(
*,
limit: int = 50,
archived: Optional[bool] = None,
folder: Optional[int] = None,
) -> listlimit=Nonefetches everything, which on a large account means many requests and flood-wait risk — fetch only what you need.- The archive folder ID is
1; all other chats are in0.
dialogs = await tg.dialogs(limit=50)edit_message
asyncEdits the text, media or buttons of a sent message. Normally only your own messages; channel posts need the edit_messages admin right.
await tg.edit_message(
entity: Union[int, str, Any],
message_id: int,
text: Optional[str] = None,
*,
file: Optional[Any] = None,
parse_mode: Optional[str] = 'md',
link_preview: bool = True,
buttons: Optional[Any] = None,
) -> Any- Editing is time-limited: old messages give
MESSAGE_EDIT_TIME_EXPIRED(the threshold isedit_time_limitin the client config). - If nothing changed, Telegram returns
MESSAGE_NOT_MODIFIED.
await tg.edit_message("me", message_id, "Tahrirlangan matn")forward_messages
asyncForwards messages from from_entity to to_entity (argument order: destination, IDs, source). drop_author=True hides the original author; schedule delays delivery.
await tg.forward_messages(
to_entity: Union[int, str, Any],
message_ids: Iterable[int],
from_entity: Union[int, str, Any],
*,
silent: bool = False,
drop_author: bool = False,
schedule: Optional[datetime.datetime] = None,
) -> Any- Messages from a protected chat cannot be forwarded —
CHAT_FORWARDS_RESTRICTED. - The number of messages per call is limited (
forwarded_count_max).
await tg.forward_messages("me", [42], "@kanal", drop_author=True)mark_read
asyncMarks a dialog as read; with message, up to that message.
await tg.mark_read(
entity: Union[int, str, Any],
*,
message: Optional[Any] = None,
) -> Anyawait tg.mark_read("@guruh")message
asyncFetches a single message by ID; returns None instead of raising when it does not exist.
await tg.message(entity: Union[int, str, Any], message_id: int) -> Anymsg = await tg.message("@kanal", 42)msg = await tg.message("@kanal", 42)
data = await tg.download(msg)messages
asyncReads chat history, which the Bot API has no method for. Newest first; narrow with search, from_user, filter, min_id/max_id, or fetch exact messages with ids.
await tg.messages(
entity: Union[int, str, Any],
*,
limit: int = 50,
offset_id: int = 0,
min_id: int = 0,
max_id: int = 0,
search: Optional[str] = None,
from_user: Union[int, str, Any, NoneType] = None,
reverse: bool = False,
ids: Union[int, Sequence[int], NoneType] = None,
filter: Optional[Any] = None,
) -> list- Pagination: for the next page pass the smallest
message.idof the previous page asoffset_id;reverse=Truegoes oldest to newest. - With
idsthe list length is preserved — messages that are not found come back asNone.
history = await tg.messages("@durov", limit=50)mute
asyncMutes a dialog's notifications. until is a unix timestamp in seconds; without it the mute is effectively permanent.
await tg.mute(
entity: Union[int, str, Any],
*,
until: Union[int, datetime.datetime, NoneType] = None,
) -> Anyuntil=0is also treated as permanent — useunmute()to turn notifications back on.
pin_dialog
asyncPins a dialog to the top of the chat list, or unpins it with pinned=False.
await tg.pin_dialog(entity: Union[int, str, Any], *, pinned: bool = True) -> Any- The number of pinned dialogs is limited (
dialogs_pinned_limit_defaultfor non-Premium accounts).
pin_message
asyncPins a message in a chat, silently by default (notify=False). pm_oneside=True pins it only on your side of a private chat.
await tg.pin_message(
entity: Union[int, str, Any],
message_id: int,
*,
notify: bool = False,
pm_oneside: bool = False,
) -> Any- Groups and channels require the pin right, otherwise
CHAT_ADMIN_REQUIREDorPIN_RESTRICTED.
await tg.pin_message("me", 42, notify=False)react
asyncAdds an emoji reaction to a message; emoji=None removes it and big=True plays the larger animation.
await tg.react(
entity: Union[int, str, Any],
message_id: int,
emoji: Optional[str] = None,
*,
big: bool = False,
) -> Any- Admins can restrict which reactions are allowed (
available_reactionsinchat_full()); a disallowed emoji givesREACTION_INVALID.
await tg.react("@guruh", 42, "👍")scheduled
asyncScheduled messages in a chat that have not been sent yet — those queued with send_message(..., schedule=...) or send_file(..., schedule=...).
await tg.scheduled(entity: Union[int, str, Any]) -> Anyscheduled = await tg.scheduled("me")send_message
asyncSends a message to a private chat, group or channel; entity is an @username, numeric ID, t.me/... link or "me" (Saved Messages). Supports replies, files, buttons, silent delivery and comments on channel posts (comment_to).
await tg.send_message(
entity: Union[int, str, Any],
message: str = '',
*,
reply_to: Optional[int] = None,
parse_mode: Optional[str] = 'md',
link_preview: bool = True,
file: Optional[Any] = None,
silent: bool = False,
schedule: Optional[datetime.datetime] = None,
buttons: Optional[Any] = None,
formatting_entities: Optional[list] = None,
comment_to: Optional[int] = None,
) -> Anyschedule=datetimequeues the message on Telegram's servers and Telegram sends it at that time — your app does not need to be running.- The default
parse_mode="md"turns**,__,~~or[text](url)in user-supplied text into formatting. Useparse_mode=Nonefor raw text. - Unsolicited bulk messages are spam, and accounts that use the Telegram API for spamming are banned permanently.
await tg.send_message("@mening_guruhim", "Salom!", silent=True)send_poll
asyncSends a poll or a quiz (quiz=True): options are the answers, multiple allows several choices, public shows who voted for what.
await tg.send_poll(
entity: Union[int, str, Any],
question: str,
options: Sequence[str],
*,
multiple: bool = False,
quiz: bool = False,
correct: Optional[int] = None,
public: bool = False,
) -> Any- A quiz requires
correct(zero-based index of the right answer); without it the SDK raisesConfigurationErrorbefore sending anything.
await tg.send_poll("@guruh", "Qaysi kun?", ["Dushanba", "Juma"], multiple=False)unmute
asyncTurns a dialog's notifications back on.
await tg.unmute(entity: Union[int, str, Any]) -> Anyunpin_message
asyncUnpins a message; without message_id it unpins every pinned message in the chat.
await tg.unpin_message(
entity: Union[int, str, Any],
message_id: Optional[int] = None,
) -> Anyawait tg.unpin_message("me", 42)Media & files
Send photos, videos, documents and albums; download media and pre-upload files.
downloadsend_albumsend_fileupload
download
asyncDownloads a message's media: returns bytes by default, or writes to disk and returns the path when file is a path.
await tg.download(message: Any, *, file: Any = <class 'bytes'>) -> Anymsg = await tg.message("@kanal", 42)
data = await tg.download(msg)send_album
asyncSends several files as one album (media group); accepts caption and the other send_file() parameters.
await tg.send_album(
entity: Union[int, str, Any],
files: Sequence[Any],
*,
caption: Optional[str] = None,
**kwargs: Any,
) -> Any- Telegram allows up to 10 media per album; with more files Telethon splits them into several albums of 10.
send_file
asyncSends a photo, video, document, voice message or round video note; file is a path, bytes, file object, URL or an upload() result. Supports caption, reply_to, silent and schedule.
await tg.send_file(
entity: Union[int, str, Any],
file: Any,
*,
caption: Optional[str] = None,
force_document: bool = False,
voice_note: bool = False,
video_note: bool = False,
silent: bool = False,
reply_to: Optional[int] = None,
thumb: Optional[Any] = None,
attributes: Optional[list] = None,
schedule: Optional[datetime.datetime] = None,
parse_mode: Optional[str] = 'md',
) -> Any- Use
force_document=Trueto deliver the file as-is, as a document. voice_note=Truesends a voice message;video_note=Truesends a round video note.
await tg.send_file("me", "photo.jpg", caption="Rasm")upload
asyncPre-uploads a file to Telegram and returns a handle you can pass to send_file() later (filename sets the visible file name).
await tg.upload(file: Any, *, filename: Optional[str] = None) -> Any- Uploaded parts are only kept temporarily, from minutes to a few hours, so use the result promptly.
Stories
Posting, reading, editing and deleting stories. Posting as a user requires Telegram Premium.
can_send_storydelete_storyedit_storysend_storystoriesstory_targets
can_send_story
asyncChecks whether a story can be posted, before posting it (stories.canSendStory). On success it returns the number of free story slots (count); Telegram's documentation requires this call before sending a story.
await tg.can_send_story(entity: Union[int, str, Any] = 'me') -> Anyentity="me"checks your own account; pass a channel or supergroup to check posting as that peer.- Errors in SDK terms:
PREMIUM_ACCOUNT_REQUIRED— the user has no Premium;CHAT_ADMIN_REQUIRED— nopost_storiesright in the channel (PermissionError);BOOSTS_REQUIRED— the channel lacks boosts;STORIES_TOO_MUCH— the active-story limit is reached. - Weekly and monthly limits (
STORY_SEND_FLOOD_WEEKLY/STORY_SEND_FLOOD_MONTHLY) arrive asRateLimitError, with the wait inretry_after. - It is not a guarantee: if the state changes between the check and
send_story()— a story posted from another device, say — the same errors come back at posting time.
check = await tg.can_send_story("me")delete_story
asyncDeletes one story or a list of them (stories.deleteStories) and returns the IDs that were actually deleted.
await tg.delete_story(
entity: Union[int, str, Any],
story_id: Union[int, Iterable[int]],
) -> Any- Active, profile-pinned and archived stories are all removed; this cannot be undone.
- A non-existent ID is not an error — it simply does not appear in the returned list.
await tg.delete_story("me", [42, 43])edit_story
asyncEdits a published story (stories.editStory): only the fields you pass are sent, everything else stays as it was. Use it to replace the caption or tighten privacy.
await tg.edit_story(
entity: Union[int, str, Any],
story_id: int,
*,
file: Optional[Any] = None,
caption: Optional[str] = None,
privacy: Union[str, Sequence[Any], NoneType] = None,
allow: Optional[Sequence[Union[int, str, Any]]] = None,
deny: Optional[Sequence[Union[int, str, Any]]] = None,
photo: Optional[bool] = None,
thumb: Optional[Any] = None,
parse_mode: Optional[str] = None,
entities: Optional[list] = None,
media_areas: Optional[list] = None,
) -> Anyprivacydefaults toNonehere (unlikesend_story, where privacy is mandatory and defaults to"everyone"): leave it out and the story keeps its current audience.- Passing no fields at all raises
ConfigurationError— an empty edit is a meaningless request for Telegram. - With
filethe media is re-uploaded entirely (same 30 MB limit): per Telegram's docs the file attributes are welded to the media, so there is no other way to change them.
await tg.edit_story("me", 42, caption="Tahrirlangan matn")send_story
asyncPosts a story (stories.sendStory): entity is the author ("me" or a channel/supergroup) and file is a vertical photo or video. The response is an Updates object carrying the new story id (updateStoryID).
await tg.send_story(
entity: Union[int, str, Any],
file: Any,
*,
caption: Optional[str] = None,
period: Union[int, datetime.timedelta, NoneType] = None,
privacy: Union[str, Sequence[Any], NoneType] = 'everyone',
allow: Optional[Sequence[Union[int, str, Any]]] = None,
deny: Optional[Sequence[Union[int, str, Any]]] = None,
pinned: bool = False,
noforwards: bool = False,
photo: Optional[bool] = None,
thumb: Optional[Any] = None,
parse_mode: Optional[str] = None,
entities: Optional[list] = None,
media_areas: Optional[list] = None,
albums: Optional[Sequence[int]] = None,
) -> Any- Telegram's rules: posting as a user requires **Telegram Premium** on the account (otherwise
PREMIUM_ACCOUNT_REQUIRED); posting as a channel requires thepost_storiesadmin right and enough boosts. Callcan_send_story(entity)first so the error appears before the file is uploaded, not after. fileis a path, bytes, a file object or a preparedInputFile, at most 30 MB — the limit is checked locally. The type comes from the extension (.jpg/.pngmeans photo, anything else video); force it withphoto=True/False.periodis how many seconds until the story is archived, and only four values are accepted: 21600 (6 h), 43200 (12 h), 86400 (24 h) and 172800 (48 h); atimedeltaworks too. Without Premium only 86400 is allowed — Telegram rejects the rest. If omitted, the field is not sent and Telegram applies 24 hours.privacy:"everyone"(default),"contacts","close_friends"(the list is managed in the Telegram app) or"selected", which requiresallow.denyis the exception list and is appended after the other rules ("contacts, except this person").pinned=Truekeeps the story on the profile after it expires;noforwards=Trueblocks forwarding and saving.- The
captionlength limit comes from Telegram's config and is not documented as a number, so the SDK does not check it. Withoutparse_modethe text is sent unformatted: story formatting depends on account configuration and is not available everywhere. - Telegram does not document aspect ratio or duration as numbers, so the SDK does not validate them — non-vertical media is cropped badly in the apps.
await tg.send_story("me", "story.mp4", caption="Salom!", period=86400,
privacy="contacts", pinned=False)stories
asyncReturns a peer's currently active stories (stories.getPeerStories): a stories.PeerStories object holding StoryItem entries. With ids=[...] it fetches exactly those stories instead (stories.getStoriesByID).
await tg.stories(
entity: Union[int, str, Any] = 'me',
*,
ids: Union[int, Iterable[int], NoneType] = None,
) -> Any- Some entries can arrive as
StoryItemSkipped— Telegram saves bandwidth and omits the full data; request those individually withstories(entity, ids=[...]). - Expired stories are not here: they live in the archive (
stories.getStoriesArchive) or among the profile's pinned ones (stories.getPinnedStories) — reach those throughraw().
items = await tg.stories("me")story_targets
asyncReturns the channels and supergroups you can post stories as (stories.getChatsToSend). Useful for a "post as" picker in your interface.
await tg.story_targets() -> Any- Being on the list does not mean posting will succeed: boosts and limits are checked by
can_send_story(entity), so call it anyway.
targets = await tg.story_targets()Contacts, users & blocking
Contacts, blocking, user profiles and resolving usernames or IDs to Telegram objects.
add_contactblockblockedcommon_chatscontactsdelete_contactentityresolveunblockuser_full
add_contact
asyncAdds a contact by phone number (contacts.importContacts). If the number is on Telegram, the user comes back in imported and users.
await tg.add_contact(phone: str, first_name: str, *, last_name: str = '') -> Any- No exception is raised when the number is not found —
importedis simply empty, so check the result. retry_contactslists contacts not imported due to a system limit; retry them later.
await tg.add_contact("+998901234567", "Ali", last_name="Valiyev")block
asyncBlocks a user (contacts.block); undo with unblock().
await tg.block(user: Union[int, str, Any]) -> Anyawait tg.block("@spam_bot")
await tg.unblock("@spam_bot")blocked
asyncLists blocked users, up to 100 by default.
await tg.blocked(*, limit: int = 100) -> Anyblocked = await tg.blocked(limit=100)common_chats
asyncGroups and channels you share with the given user, up to 100 by default.
await tg.common_chats(user: Union[int, str, Any], *, limit: int = 100) -> Anychats = await tg.common_chats("@foydalanuvchi")contacts
asyncThe account's contact list (contacts.getContacts): contact entries plus their users objects.
await tg.contacts() -> Anycontacts = await tg.contacts()delete_contact
asyncRemoves a user from your contacts.
await tg.delete_contact(user: Union[int, str, Any]) -> Anyawait tg.delete_contact("@foydalanuvchi")entity
asyncResolves an @username, numeric ID, t.me/... link or phone number to a Telegram object (User, Chat, Channel). Other methods use it internally.
await tg.entity(entity: Union[int, str, Any]) -> Any- Numeric IDs only work for objects already in the session's in-memory cache (e.g. after
dialogs()orparticipants()); the session string does not persist that cache. Not found raisesNotFoundError.
resolve
asyncLooks up an @username directly on Telegram (contacts.resolveUsername), with or without the @. The response has peer, users and chats.
await tg.resolve(username: str) -> Any- Unlike the cache-aware
entity(), every call hits the server, so preferentity()for repeated lookups.
unblock
asyncUnblocks a user.
await tg.unblock(user: Union[int, str, Any]) -> Anyawait tg.block("@spam_bot")
await tg.unblock("@spam_bot")user_full
asyncFull user info (users.getFullUser): bio, number of common chats and other profile fields.
await tg.user_full(user: Union[int, str, Any]) -> Anyuser = await tg.user_full("@durov")Groups & channels
Create, join, leave, delete; title, description, photo, slow mode and default permissions.
chat_fullcreate_channelcreate_groupdelete_chatjoinleaveset_aboutset_default_permissionsset_photoset_slowmodeset_title
chat_full
asyncFull group or channel info: description, member count, invite link, linked discussion group, pending join requests. Uses channels.getFullChannel for supergroups/channels and messages.getFullChat for basic groups.
await tg.chat_full(entity: Union[int, str, Any]) -> Any- Useful flags inside
full_chat:can_view_stats(statistics available),can_view_participants(member list visible to you),slowmode_seconds(current slow mode).
info = await tg.chat_full("@kanal")create_channel
asyncCreates a channel (broadcast=True, default) or a supergroup (broadcast=False); about is the description.
await tg.create_channel(title: str, *, about: str = '', broadcast: bool = True) -> Any- The new channel has no public username, so it starts out private.
channel = await tg.create_channel("Yangiliklar", about="Kompaniya yangiliklari")create_group
asyncCreates a group: megagroup=True (default) makes a supergroup, megagroup=False a basic group with users.
await tg.create_group(
title: str,
users: Sequence[Union[int, str, Any]],
*,
megagroup: bool = True,
about: str = '',
) -> Any- With
megagroup=Truetheusersare not added — invite them afterwards withinvite().aboutis ignored for basic groups. - Basic groups hold up to 200 members, supergroups up to 200,000.
group = await tg.create_group("Playground guruhi", ["@do'stim"], megagroup=True)delete_chat
asyncPermanently deletes a group or channel for everyone; members and history cannot be restored. Requires creator rights, otherwise PermissionError.
await tg.delete_chat(entity: Union[int, str, Any]) -> Any- Channels and supergroups with more than 1,000 members cannot be deleted through the API —
CHANNEL_TOO_LARGE.
await tg.delete_chat("@mening_kanalim")join
asyncJoins a group or channel from a public @username or a t.me/+... / t.me/joinchat/... invite link — the SDK picks the right TL request.
await tg.join(entity: Union[str, int, Any]) -> Any- If the link requires admin approval, Telegram returns
INVITE_REQUEST_SENT, meaning the request was submitted (anApiErrorin the SDK). - An account can only be in a limited number of channels and supergroups; beyond that you get
CHANNELS_TOO_MUCH.
await tg.join("@kanal")leave
asyncLeaves a group or channel; for basic groups your copy of the history is cleared too.
await tg.leave(entity: Union[int, str, Any]) -> Anyawait tg.leave("@kanal")set_about
asyncChanges a group or channel description (about).
await tg.set_about(entity: Union[int, str, Any], about: str) -> Any- Length is limited: too long gives
CHAT_ABOUT_TOO_LONG, unchanged text givesCHAT_ABOUT_NOT_MODIFIED.
Sets default permissions for every member (e.g. send_messages=False to stop everyone posting). As in restrict(), False means forbidden.
await tg.set_default_permissions(entity: Union[int, str, Any], **rights: bool) -> Anyview_messagescannot be used here; it only applies to banning an individual member.- Telethon only supports this for supergroups and channels; on a basic group the SDK raises
NotFoundError.
set_photo
asyncUploads and sets a group or channel photo from a path, bytes or a file object.
await tg.set_photo(entity: Union[int, str, Any], file: Any) -> Anyawait tg.set_photo("@guruh", "logo.png")set_slowmode
asyncEnables slow mode in a supergroup: each member can send one message every seconds seconds; 0 turns it off.
await tg.set_slowmode(entity: Union[int, str, Any], seconds: int = 0) -> Any- Supergroups only. A duration Telegram does not accept returns
SECONDS_INVALID.
await tg.set_title("@guruh", "Yangi nom")
await tg.set_slowmode("@guruh", 30)set_title
asyncRenames a group or channel; requires the change_info right.
await tg.set_title(entity: Union[int, str, Any], title: str) -> Anyawait tg.set_title("@guruh", "Yangi nom")
await tg.set_slowmode("@guruh", 30)Members & moderation
Member lists, inviting, kicking, banning, restricting and admin rights.
adminsbanbanneddemoteinvitekickmute_memberparticipantspermissionspromoterestrictunban
admins
asyncOnly the admins, with no limit; each user's participant attribute holds their role and rights.
await tg.admins(entity: Union[int, str, Any]) -> listadmins = await tg.admins("@guruh")ban
asyncRemoves a member and prevents them from rejoining. until (datetime) sets a temporary ban; without it the ban is permanent.
await tg.ban(
entity: Union[int, str, Any],
user: Union[int, str, Any],
*,
until: Optional[datetime.datetime] = None,
) -> Any- Telegram treats durations over 366 days or under 30 seconds as permanent.
- Works in supergroups and channels (needs
ban_users); in basic groups usekick().
await tg.ban("@guruh", "@foydalanuvchi")banned
asyncBanned (kicked) members, up to 100 by default.
await tg.banned(entity: Union[int, str, Any]) -> list- For members who are only restricted:
participants(entity, filter=tg.tl().types.ChannelParticipantsBanned(q="")).
banned = await tg.banned("@guruh")demote
asyncRemoves admin rights and returns the user to a regular member.
await tg.demote(entity: Union[int, str, Any], user: Union[int, str, Any]) -> Anyawait tg.demote("@guruh", "@foydalanuvchi")invite
asyncAdds users to a group or channel: one request for supergroups and channels, one per user for basic groups.
await tg.invite(
entity: Union[int, str, Any],
users: Sequence[Union[int, str, Any]],
) -> Any- If a user's privacy settings forbid it you get
USER_PRIVACY_RESTRICTED(PermissionError); send them an invite link instead. missing_inviteesin a supergroup/channel response lists users who could not be added.- Mass-adding strangers is spam, and accounts that use the Telegram API for spamming are banned permanently.
await tg.invite("@guruh", ["@foydalanuvchi"])kick
asyncRemoves a member without banning them, so they can rejoin via a link. Use ban() to block them.
await tg.kick(entity: Union[int, str, Any], user: Union[int, str, Any]) -> Any- In supergroups and channels Telethon does this as a ban followed immediately by an unban.
await tg.kick("@guruh", "@foydalanuvchi")mute_member
asyncStops a member from sending messages — shorthand for restrict(send_messages=False); add until for a time limit.
await tg.mute_member(
entity: Union[int, str, Any],
user: Union[int, str, Any],
*,
until: Optional[datetime.datetime] = None,
) -> Anyparticipants
asyncLists members of a group or channel, which the Bot API cannot do. search matches names or usernames, filter selects admins, bots, banned users and so on, limit=None fetches all.
await tg.participants(
entity: Union[int, str, Any],
*,
limit: Optional[int] = 100,
search: Optional[str] = None,
aggressive: bool = False,
filter: Optional[Any] = None,
) -> list- Channel subscriber lists need admin rights (
CHAT_ADMIN_REQUIRED→PermissionError); supergroups may hide the list — checkcan_view_participantsinchat_full(). - The
aggressiveflag does nothing in current Telethon versions and is kept only for backward compatibility. - Messaging people from the list unsolicited or mass-adding them to other chats counts as spam and gets accounts banned.
members = await tg.participants("@guruh", limit=200)permissions
asyncOne member's status and rights in a chat: creator, admin, restricted, and what they may do (Telethon ParticipantPermissions).
await tg.permissions(entity: Union[int, str, Any], user: Union[int, str, Any]) -> Anyrights = await tg.permissions("@guruh", "@foydalanuvchi")promote
asyncMakes a member an admin; each right is its own flag (delete_messages, ban_users, pin_messages ...) and title sets the custom title. add_admins defaults to False, so the new admin cannot appoint others.
await tg.promote(
entity: Union[int, str, Any],
user: Union[int, str, Any],
*,
title: Optional[str] = None,
change_info: bool = True,
post_messages: bool = True,
edit_messages: bool = True,
delete_messages: bool = True,
ban_users: bool = True,
invite_users: bool = True,
pin_messages: bool = True,
add_admins: bool = False,
manage_call: bool = True,
anonymous: bool = False,
) -> Any- Basic groups have no granular rights — the user simply becomes an admin.
anonymous=Truehides the admin from the member list and stops them posting as their own account.
await tg.promote("@guruh", "@foydalanuvchi", title="Moderator", ban_users=True)restrict
asyncRestricts individual rights of a member: messages, media, stickers, GIFs, links, polls and more. Note: False forbids, True allows; until sets a duration.
await tg.restrict(
entity: Union[int, str, Any],
user: Union[int, str, Any],
*,
until: Optional[datetime.datetime] = None,
send_messages: bool = True,
send_media: bool = True,
send_stickers: bool = True,
send_gifs: bool = True,
send_games: bool = True,
send_inline: bool = True,
embed_links: bool = True,
send_polls: bool = True,
change_info: bool = True,
invite_users: bool = True,
pin_messages: bool = True,
embed_link_previews: bool = True,
) -> Any- Supergroups only: basic groups do not support per-member restrictions (the SDK raises
NotFoundError). - Omitted flags default to
True, so each call rewrites the full set of rights and earlier restrictions are not kept.
await tg.restrict("@guruh", "@foydalanuvchi", send_messages=False)unban
asyncLifts a ban and restrictions. The user is not re-added automatically; they must rejoin or be invited again.
await tg.unban(entity: Union[int, str, Any], user: Union[int, str, Any]) -> Anyawait tg.unban("@guruh", "@foydalanuvchi")Invite links & join requests
Create and revoke invite links, list and approve join requests.
approve_join_requestcreate_invite_linkinvite_linkjoin_requestsrevoke_invite_link
approve_join_request
asyncApproves (approved=True) or dismisses (approved=False) a join request.
await tg.approve_join_request(
entity: Union[int, str, Any],
user: Union[int, str, Any],
*,
approved: bool = True,
) -> Anyawait tg.approve_join_request("@guruh", 123456789, approved=True)create_invite_link
asyncCreates an additional invite link: title (visible to admins only), expire_date, usage_limit (max joins) and request_needed (admin approval for each join).
await tg.create_invite_link(
entity: Union[int, str, Any],
*,
title: Optional[str] = None,
expire_date: Optional[datetime.datetime] = None,
usage_limit: Optional[int] = None,
request_needed: bool = False,
) -> Any- Requests from a
request_needed=Truelink appear injoin_requests()and are handled withapprove_join_request().
link = await tg.create_invite_link("@guruh", title="Reklama", usage_limit=100)invite_link
asyncCreates a new unrestricted invite link for the chat (messages.exportChatInvite); the URL is in the response's link field.
await tg.invite_link(entity: Union[int, str, Any]) -> Any- Each call creates an additional link and does not revoke existing ones. The chat's current link is
full_chat.exported_inviteinchat_full(). - For expiry, usage limits or admin approval use
create_invite_link().
link = await tg.invite_link("@guruh")join_requests
asyncPending join requests awaiting admin approval (messages.getChatInviteImporters), up to 50 by default; the response has importers and users.
await tg.join_requests(entity: Union[int, str, Any], *, limit: int = 50) -> Anyrequests = await tg.join_requests("@guruh")revoke_invite_link
asyncRevokes an invite link so nobody can join through it; link is the full URL.
await tg.revoke_invite_link(entity: Union[int, str, Any], link: str) -> Any- Admins can still see information about the revoked link.
await tg.revoke_invite_link("@guruh", "https://t.me/+AbCdEf")Search & statistics
Search within a chat or across all chats, channel statistics.
search
asyncSearches text within one chat; from_user limits results to one sender. Shorthand for messages(search=...).
await tg.search(
entity: Union[int, str, Any],
query: str,
*,
limit: int = 50,
from_user: Union[int, str, Any, NoneType] = None,
) -> listfound = await tg.search("@guruh", "narx", limit=50)
everywhere = await tg.search_global("socialift")search_global
asyncSearches messages across all your chats (messages.searchGlobal) and returns the raw TL response (messages, chats, users).
await tg.search_global(query: str, *, limit: int = 50) -> Any- Only the first page (
limitresults) is returned; for more, callraw()withoffset_rate,offset_peerandoffset_id.
found = await tg.search("@guruh", "narx", limit=50)
everywhere = await tg.search_global("socialift")stats
asyncChannel statistics (stats.getBroadcastStats): followers, post views and shares, growth graphs. For channel admins.
await tg.stats(entity: Union[int, str, Any]) -> Any- Stats are only available for channels above a server-side size threshold — check
full_chat.can_view_statsinchat_full(). - Telegram requires this request to go to the channel's
stats_dcdata center, while the SDK sends it to the current one, soSTATS_MIGRATEcan occur. In that case, and for supergroup stats, useawait tg.client.get_stats("@channel"), which routes to the right DC.
stats = await tg.stats("@kanal")Low level: raw TL requests
Call any MTProto method through Telethon when the SDK has no wrapper for it.
raw
asyncSends any Telethon TL request (functions.*Request) and maps errors into the SDK hierarchy (RateLimitError, AuthError, PermissionError, NotFoundError). Use it for features the SDK does not wrap.
await tg.raw(request: Any) -> Any- Request fields accept an
@usernameor ID; Telethon converts them to the requiredInput*type. - Parameter names match the TL schema. Unlike
tg.client(...), errors arrive as SDK exceptions rather than Telethon ones.
from telethon import functions
config = await tg.raw(functions.help.GetConfigRequest())tl
staticmethodReturns the telethon.tl module: tg.tl().functions for requests and tg.tl().types for types such as filters and rights. Pairs with raw() and filter= arguments.
TelegramAccount.tl()- A static method that works without a connection, but
telethonmust be installed.
Frequently asked questions
What is the difference between a Telegram bot and TelegramAccount (userbot)?
A bot uses the Bot API with a BotFather token: it cannot start a conversation with a user, and the Bot API has no method for chat history or a full member list. TelegramAccount speaks MTProto as a regular user, so it can read history, list members, join groups and create channels. Use a bot for a service many people talk to, and TelegramAccount to automate your own account and the groups you run.
How do I get a Telegram api_id and api_hash?
Log in to my.telegram.org with your phone number and fill in the application form under API development tools; you receive an api_id (integer) and api_hash (string). Each phone number can currently have only one api_id. In the SDK, TelegramAccount.from_env() reads them from TELEGRAM_API_ID and TELEGRAM_API_HASH.
How should I store a Telethon session string securely?
The string returned by sign_in() or export_session() contains the authorization key: whoever has it can use the account without a code or password. Store it encrypted and keep it out of logs and error messages. If it leaks, call log_out() or terminate it with reset_session() from another session.
What should I do about FLOOD_WAIT errors?
FLOOD_WAIT_X means Telegram requires you to wait X seconds. Telethon sleeps through waits of up to 60 seconds by itself (the call just takes longer); longer waits surface as RateLimitError with retry_after, and you decide what to do. Accounts that use the Telegram API for flooding, spamming or faking subscriber and view counts are banned permanently.
Can I post a Telegram story from Python?
Yes, but only as a user account: send_story() calls MTProto's stories.sendStory, while the Bot API cannot post stories at all. Telegram's rules: posting as a user needs Premium, posting as a channel needs the post_stories right and enough boosts. A story lasts 6, 12, 24 or 48 hours (only 24 without Premium), and the media must be vertical and at most 30 MB. Check with can_send_story() before posting.
How do I get all members of a Telegram group in Python?
await tg.participants("@group", limit=None) returns a list of Telethon User objects; narrow it with search or filter. Listing a channel's subscribers requires admin rights (otherwise PermissionError), and supergroup admins may have hidden the member list. Check can_view_participants in the chat_full() result first.