Entities & Peers
Every MTProto call addresses a peer — a user, chat, or channel — and most of them require an access_hash your account has already seen. This page is the model behind that, and the handful of methods you use to navigate it.
Three shapes#
Telegram represents the same peer three different ways depending on what you are doing with it. Get this distinction right and the rest of the API stops feeling arbitrary.
- Peer — identity only.
Api.PeerUser,Api.PeerChat, andApi.PeerChannelcarry an id and nothing else. You see these on incoming updates (message.peerId,message.fromId) and inside other TL structures. - InputPeer — id plus
access_hash. This is what almost every request actually wants:Api.InputPeerUser,Api.InputPeerChat,Api.InputPeerChannel, and the shortcutApi.InputPeerSelffor your own account. - Full entity — the resolved
Api.User/Api.Chat/Api.Channelobject with every field on it. You get these back fromgetEntity.
import { Api } from "teleproto";
// Peer — identity only, no auth material
new Api.PeerUser({ userId });
new Api.PeerChat({ chatId });
new Api.PeerChannel({ channelId });
// InputPeer — what Api.* methods actually want
new Api.InputPeerUser({ userId, accessHash });
new Api.InputPeerChat({ chatId });
new Api.InputPeerChannel({ channelId, accessHash });
new Api.InputPeerSelf();The access_hash is per-account. Telegram derives it from your auth key, so a hash you collected on one session will not work from another. Don't persist input peers across logins — let the session cache rebuild them.
getEntity vs getInputEntity#
Two resolver methods, two jobs. getEntity(thing) gives you the full object — and round-trips to Telegram if the peer is not in cache. getInputEntity(thing) returns just the input shape and avoids the network call when it can.
// Full entity — round-trips to Telegram if the peer is unknown.
// Returns Api.User / Api.Chat / Api.Channel with every field populated.
const user = await client.getEntity("durov");
console.log(user.firstName, user.username);
// Input shape only — pulled from the session cache when possible.
// This is what you pass to raw Api.* calls.
const inputPeer = await client.getInputEntity("durov");
await client.invoke(
new Api.messages.GetHistory({ peer: inputPeer, limit: 10 }),
);Rule of thumb: call getInputEntity when you're feeding a peer into an Api.* method. Call getEntity only when you need the fields — username, title, member count, profile photo.
Entity cache#
The session quietly records access_hash values from every update and response that flows through the client. Once a peer has been seen, both resolvers can answer locally. If your account has never seen the peer, getEntity has to resolve it on the wire — and that can trip a FloodWait if you do it in a tight loop.
import { TelegramClient } from "teleproto";
import { StoreSession } from "teleproto/sessions";
const client = new TelegramClient(
new StoreSession("my-account"),
apiId,
apiHash,
{ connectionRetries: 5 },
);
await client.connect();
// Warm the entity cache once at startup so later lookups
// resolve from the session without a network round-trip.
for await (const dialog of client.iterDialogs({})) {
// touching .entity is enough — the session records the access_hash
void dialog.entity;
}One iterDialogs pass at startup is enough to load every chat in your dialog list into the session. After that, lookups by username, id, or peer should resolve without a round-trip.
getMe and getPeerId#
Two helpers that come up constantly. getMe() returns the full Api.User for the logged-in account. getPeerId(peer, addMark?) normalizes any peer-shaped input to a numeric id — useful when you're indexing into a map or logging.
const me = await client.getMe();
console.log("Logged in as", me.username ?? me.firstName);
// Normalized numeric id — channels come back negative,
// or with the -100 prefix when addMark is true.
const id = await client.getPeerId("durov"); // 123456789
const marked = await client.getPeerId("@somechannel", true); // -1001234567890Telegram marks channel ids with a negative sign (or the -100 prefix you see in t.me links). addMark=true gives you the marked form; the default returns the bare id.
When resolution fails#
The most common failure mode is the error Cannot find any entity corresponding to... — teleproto could not reconcile what you asked for with anything the session knows. Usual causes:
- Your account has never interacted with this peer.
- The user deleted their account or was banned.
- The chat is private and your account is not a member — Telegram won't return an
access_hashfor a peer you can't see.
The fastest workaround for a stuck username or id: open the chat in Telegram Desktop with the same account, then await client.catchUp() on the next run. Once it's in your dialog list, the session will pick up the hash on its own.
Next: Updates & Events covers how Telegram pushes changes back to you over the same socket.