Skip to content

the three-layer api

puregram gives you three ways to call bot api methods. they're equivalent in capability — pick whichever reads best at the call site

ts
// 1. raw — every method, schema-typed params, fully autogenerated
await tg.api.sendMessage({ chat_id: 100, text: 'hello' })

// 2. curated shortcut — positional args, chat first
await tg.send(100, 'hello')

// 3. per-kind shortcut — chat_id filled automatically from the update
tg.onMessage(message => message.send('hello'))

layer 1 — tg.api.X(params) (raw)

the raw layer is fully autogenerated from the bot api schema. every method telegram documents is available here, typed with exact parameter shapes

ts
// all params explicit — nothing is filled for you
await tg.api.sendMessage({
  chat_id: 100,
  text: 'hello',
  parse_mode: 'HTML',
  reply_parameters: { message_id: 42 }
})

// every method that telegram supports, including less common ones
await tg.api.banChatMember({ chat_id: -1001234567890, user_id: 777 })
await tg.api.createChatInviteLink({ chat_id: -1001234567890 })
await tg.api.answerCallbackQuery({ callback_query_id: '...', text: 'done' })

when to use: when you need the full parameter surface, when you're calling a method that has no curated shortcut, or when the return value needs to be a raw TelegramX shape for interop

return type: raw bot-api interface (e.g. TelegramMessage, TelegramChatInviteLink)

layer 2 — tg.send(chat, text, params?) (curated)

the curated layer is a set of handcrafted and codegen'd shortcuts on the Telegram class that accept positional arguments and omit the fields they fill themselves

ts
// positional: chat first, content second, extras optional
await tg.send(100, 'hello')
await tg.send(100, 'hello', { parse_mode: 'HTML' })

await tg.sendPhoto(100, MediaSource.path('./cat.png'), { caption: 'cute' })
await tg.sendDocument(100, MediaSource.url('https://example.com/file.pdf'))

when to use: when you have the chat id in hand and want clean call sites without repeating chat_id: everywhere

return type: raw TelegramMessage (same as tg.api.sendMessage)

layer 3 — update.send(text, params?) (per-kind shortcut)

every message-bearing update class carries a set of codegen'd shortcuts that auto-fill the anchor — the chat_id (and other positional args) the method needs — from the update's own payload

ts
tg.onMessage((message) => {
  // chat_id is taken from message.raw.chat.id automatically
  return message.send('got it')
})

tg.onCallbackQuery((callbackQuery) => {
  // callback query answer — no chat_id needed
  return callbackQuery.answer({ text: 'thanks' })
})

when to use: inside an update handler when you're responding to the current update. this is the most common path

return type: raw TelegramMessage (same underlying tg.api.sendMessage call)

the per-kind shortcuts also include reply twinsmessage.reply(text) and message.replyWith<Media>(src) — that auto-fill reply_parameters.message_id so the response threads under the incoming message; see shortcuts for the full list

side-by-side: three ways to send the same message

ts
const CHAT_ID = 100
const TEXT = 'hello there'

// layer 1 — explicit params object
await tg.api.sendMessage({
  chat_id: CHAT_ID,
  text: TEXT,
  parse_mode: 'HTML'
})

// layer 2 — positional chat + text
await tg.send(CHAT_ID, TEXT, { parse_mode: 'HTML' })

// layer 3 — inside a handler, chat_id comes from the update
tg.onMessage(async (message) => {
  await message.send(TEXT, { parse_mode: 'HTML' })
})

all three send the same message. the only difference is how much you type

the escape hatch

for methods not yet in the codegen, or corefork-only beta methods, use tg.api.call:

ts
// raw method name + params — no type-checking on the response
await tg.api.call('someBetaMethod', { foo: 'bar' })

tg.api.call always throws on errors — there's no suppress option here

which layer should i use day to day?

layer 3 (update.send, update.delete, update.answer, ...) inside handlers. layer 2 (tg.send, tg.sendPhoto, ...) when you need to push a message outside of a handler (e.g. from a cron job). layer 1 (tg.api.X) when the curated shortcuts don't cover the parameter you need

see also