Telegram

Lightweight, dependency-free, in-memory fake of the Telegram Bot API for testing code that uses the real node-telegram-bot-api client (or any client that speaks the Telegram Bot HTTP API).

Default port: 4656

Quick start

Start the server:

import { TelegramServer } from "./services/telegram/src/server.js";

const server = new TelegramServer(4656);
await server.start();
// ... run your app/tests ...
await server.stop();

Point the real node-telegram-bot-api client at it. The client builds request URLs from a baseApiUrl option, so override it to the parlel fake:

import TelegramBot from "node-telegram-bot-api";

const TOKEN = "123456789:parlel-test-bot-token";

const bot = new TelegramBot(TOKEN, {
  baseApiUrl: "http://127.0.0.1:4656", // point at the parlel fake
  polling: false,
});

const me = await bot.getMe();
// me.username === "parlelbot"

const msg = await bot.sendMessage(555000111, "hello from parlel");
// msg.text === "hello from parlel", msg.chat.id === 555000111

// Inject an incoming user message, then poll for it:
await fetch("http://127.0.0.1:4656/__parlel/message", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ chat_id: 555000111, text: "/start" }),
});
const updates = await bot.getUpdates();
// updates[0].message.text === "/start"

Everything the bot sends (messages, media, polls, invoices, …) is captured in memory and can be inspected via the /__parlel/* endpoints (see below). The whole world is resettable.

Wire protocol

The fake speaks the exact Telegram Bot API wire protocol that node-telegram-bot-api uses:

Default seed state

reset() (and startup) seeds a small world so tests can run immediately:

Implemented operations

Grouped by area. All names match node-telegram-bot-api method names.

Bot & connection

getMe, logOut, close, getUpdates, setWebHook (setWebhook), deleteWebHook (deleteWebhook), getWebHookInfo (getWebhookInfo)

Sending messages

sendMessage, forwardMessage, copyMessage, sendPhoto, sendAudio, sendDocument, sendVideo, sendAnimation, sendVoice, sendVideoNote, sendMediaGroup, sendSticker, sendDice, sendChatAction

Location / venue / contact / poll

sendLocation, editMessageLiveLocation, stopMessageLiveLocation, sendVenue, sendContact, sendPoll, stopPoll

Editing & deleting

editMessageText, editMessageCaption, editMessageReplyMarkup, editMessageMedia, deleteMessage

Files

getUserProfilePhotos, getFile, getFileLink (client-side; resolves to /file/bot<TOKEN>/<path>)

Chat member management

banChatMember (kickChatMember), unbanChatMember, banChatSenderChat, unbanChatSenderChat, restrictChatMember, promoteChatMember, setChatAdministratorCustomTitle, setChatPermissions, getChatMember, getChatMemberCount (getChatMembersCount), getChatAdministrators, leaveChat

Invite links & join requests

exportChatInviteLink, createChatInviteLink, editChatInviteLink, revokeChatInviteLink, approveChatJoinRequest, declineChatJoinRequest

Chat metadata

getChat, setChatTitle, setChatDescription, setChatPhoto, deleteChatPhoto, pinChatMessage, unpinChatMessage, unpinAllChatMessages, setChatStickerSet, deleteChatStickerSet, setChatMenuButton, getChatMenuButton

Bot profile & commands

setMyCommands, getMyCommands, deleteMyCommands (scope-aware), setMyDescription, getMyDescription, setMyShortDescription, getMyShortDescription, setMyName, getMyName, setMyDefaultAdministratorRights, getMyDefaultAdministratorRights

Callback / inline / reactions

answerCallbackQuery, answerInlineQuery, setMessageReaction

Stickers

getStickerSet, uploadStickerFile, createNewStickerSet, addStickerToSet, setStickerPositionInSet, deleteStickerFromSet

Games

sendGame, setGameScore, getGameHighScores

Payments

sendInvoice, answerShippingQuery, answerPreCheckoutQuery

parlel control / inspection endpoints

These are not part of the Telegram API — they let tests inject incoming updates and inspect captured state. No auth required.

Method & pathPurpose
GET /healthHealth check → { "status": "ok" }
GET /Service metadata
POST /__parlel/resetReset all in-memory state to the seeded defaults
GET /__parlel/sentEvery message the bot has sent → { sent, count }
GET /__parlel/chatsAll known chats → { chats, count }
GET /__parlel/messagesAll messages across all chats → { messages, count }
GET /__parlel/callbacksRecorded answerCallbackQuery answers
POST /__parlel/updatesInject a raw Update object onto the getUpdates queue
POST /__parlel/messageConvenience: inject an incoming text message ({ chat_id?, text?, from? })

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatusNotes
Bot API HTTP method surface✅ Supported90+ methods (see list above)
{ ok, result } / { ok, error_code, description } envelopes✅ SupportedMatches the real wire format
URL query / form-urlencoded / JSON / multipart params✅ SupportedMultipart parsed best-effort for uploads
@username chat ids✅ SupportedResolved for seeded chats
getUpdates long-poll + offset confirmation✅ SupportedUpdates injected via /__parlel
Webhook config (setWebhook / getWebhookInfo)✅ SupportedStored, not actually delivered over HTTP
Incoming update injection✅ SupportedVia /__parlel/updates and /__parlel/message
Scoped bot commands✅ Supporteddefault / all_group_chats / chat-scoped
File downloads✅ Supported/file/bot<TOKEN>/<path> returns placeholder bytes
Real file storage / image processing⟳ Roadmap
Live webhook delivery to your server⟳ Roadmap
Long-poll blocking / timeout waiting⟳ Roadmap
Real auth / rate limiting✓ By design — Never throttles — local tests run at full speed, zero cost
WebSocket / MTProto transport⟳ Roadmap
Forum topics, business accounts, stars/gifts⟳ Roadmap

Error codes / shapes

Failures use the Telegram envelope with an HTTP status equal to the error_code:

{ "ok": false, "error_code": 400, "description": "Bad Request: message text is empty" }
error_codeWhen
400Bad request — missing/invalid params (empty text, unknown chat, message not found, too few poll options, etc.)
401Unauthorized — unknown bot token in the URL path
404Method name not found / route not found
500Internal error (unexpected exception)

node-telegram-bot-api surfaces these as a thrown Error with code === "ETELEGRAM" and error.response.body set to the JSON envelope above.

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

TELEGRAM_BOT_TOKEN=123456789:parlel-test-bot-token
TELEGRAM_BASE_URL=http://parlel-bridge:4656
<!-- parlel:testenv:end -->