Agent Guide
Compressed reference for AI agents using the CRMchat Telegram Raw API
Optimized for AI agent consumption. For full documentation with examples, see the Telegram Raw API reference.
Bootstrap
You need a workspaceId and accountId before calling any Telegram method. Obtain them in order:
GET /v1/organizations→ pick anidfrom the responseGET /v1/workspaces?organizationId={orgId}→ pick a workspaceidGET /v1/workspaces/{workspaceId}/telegram-accounts→ pick an account withstatus: "active"- Now call:
POST /v1/workspaces/{workspaceId}/telegram-accounts/{accountId}/call/{method}with{"params": {...}}
See Authentication for API key usage. See Pagination for list responses.
Core Concepts
Peers and accessHash
Every method targeting a user, chat, or channel requires an InputPeer object — not a bare numeric ID. InputPeer has constructors: inputPeerUser (needs userId + accessHash), inputPeerChat (needs chatId), inputPeerChannel (needs channelId + accessHash), and inputPeerSelf (no params).
The accessHash is a security token Telegram assigns per user-to-peer relationship. You cannot guess it. Obtain one by:
contacts.resolveUsername— when you know the @usernamecontacts.search— when you know the display namemessages.getDialogs— from existing conversations (users/chats arrays contain accessHash)
Cache the accessHash for your session — it doesn't change.
Some channel-specific methods (joinChannel, leaveChannel, getFullChannel) require InputChannel (inputChannel constructor) instead of InputPeer — same fields (channelId + accessHash), different type name.
randomId
Methods that create messages (sendMessage, sendMedia, etc.) require a randomId — a unique numeric string used for deduplication. Reusing one silently drops the message.
Response Joins
Methods like getDialogs and search return separate arrays (dialogs, messages, users, chats) that must be joined client-side by ID. For example, a dialog's peer.userId matches a user's id in the users array, and a dialog's topMessage matches a message's id in the messages array. Extract accessHash from the matched user/chat object.
Type Conventions
See the type conversion table for how TL types map to JSON (long → string, bytes → base64, constructors → {"_": "constructorName", ...}).
Workflows
Send a message by username
Resolve the username first: contacts.resolveUsername → extract userId and accessHash from users[0] in the response → messages.sendMessage with inputPeerUser peer + message + randomId.
Send a message by display name
contacts.search with query → find the target in users array (check myResults for contacts, results for global) → messages.sendMessage with the resolved peer.
Send a message to an existing conversation
messages.getDialogs → find the target dialog → extract peer info from users/chats arrays by matching IDs → messages.sendMessage.
Get unread conversations
messages.getDialogs → filter dialogs where unreadCount > 0 → join with users/chats arrays for names and messages array for last message text.
Read chat history
messages.getHistory with the inputPeerUser/inputPeerChannel and a limit.
Mark messages as read
messages.readHistory with the peer and maxId (the ID of the last message to mark as read).
Edit a sent message
messages.editMessage with the peer, message id, and new message text.
Delete messages
In private chats / basic groups: messages.deleteMessages with id array and revoke: true. In channels / supergroups: channels.deleteMessages with inputChannel and id array.
Search users or channels
contacts.search with query and limit. Response separates myResults (your contacts) from results (global). Actual user/chat objects are in separate users[] and chats[] arrays.
Search messages globally
messages.searchGlobal with query q, limit, and a filter (use inputMessagesFilterEmpty for all types).
Join or leave a channel
channels.joinChannel / channels.leaveChannel with inputChannel (not inputPeerChannel). You need the channel's accessHash — obtain it via contacts.resolveUsername or contacts.search.
Update own profile
account.updateProfile with any combination of firstName, lastName, about. To change username: account.updateUsername.
Get chat folder contents
messages.getDialogFilters → find the target folder by title → extract includePeers from that folder.
Essential Methods
Messages: sendMessage, editMessage, deleteMessages, getHistory, getDialogs, readHistory, searchGlobal, sendMedia
Contacts: resolveUsername, search, getContacts
Channels: joinChannel, leaveChannel, getFullChannel, deleteMessages
Account: updateProfile, updateUsername
Users: getFullUser, getMe
For parameter details on any method, fetch its OpenAPI spec: /v1/tl-spec/{namespace}/{method}.json (e.g. /v1/tl-spec/messages/sendMessage.json). Per-namespace: /v1/tl-spec/{namespace}.json. Full spec: /v1/tl-spec/all.json.
Blocked Methods
Entire namespaces auth.*, updates.*, mtcute.*, and smsjobs.* are blocked. Individual dangerous methods (account.deleteAccount, account.resetAuthorization, account.changePhone) are blocked. All methods requiring InputFile (file uploads) are blocked.
Pitfalls
- accessHash is always required for users and channels — bare IDs won't work
- randomId must be unique per message — reuse causes silent dedup
- Dialog/search responses need client-side joins — data is split across parallel arrays
- Channels use InputChannel, not InputPeer —
joinChannel,leaveChannel,getFullChanneltakeinputChannelconstructor - Account must be active — check
statusfield from the bootstrap step 3 response
External References
- Telegram TL Schema Reference — deep dives into constructors and types
- CRMchat API Docs — REST API endpoints, authentication, pagination