CRMchat APIBeta
Telegram API

Agent Guide

Compressed reference for AI agents using the CRMchat Telegram Raw API

View in Markdown

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:

  1. GET /v1/organizations → pick an id from the response
  2. GET /v1/workspaces?organizationId={orgId} → pick a workspace id
  3. GET /v1/workspaces/{workspaceId}/telegram-accounts → pick an account with status: "active"
  4. 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 @username
  • contacts.search — when you know the display name
  • messages.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 InputPeerjoinChannel, leaveChannel, getFullChannel take inputChannel constructor
  • Account must be active — check status field from the bootstrap step 3 response

External References

On this page