Reference
Sidekick constructor
| Parameter | Type | Description |
|---|---|---|
apiKeyrequired | string | Your API key (sk_live_…). Generate in settings. settings → |
platformIdrequired | string | Platform public ID (plt_…). platforms dashboard → |
baseUrl | string | API base URL. Default: "https://sidekick-ads.com". |
timeout | number | Request timeout in seconds. Default: 3.0. |
platform | string | Surface type. Default: "telegram". |
Required fields.
inject()
The only method you call. Async, never throws — on any error returns the original message and keyboard unchanged.
| Parameter | Type | Description |
|---|---|---|
userIdrequired | number | Finite numeric Telegram user ID (ctx.from.id). Telegram User docs → |
messagerequired | string | Non-empty LLM response text to inject an ad into. |
languageCoderequired | string | Required by the API. The SDK signature allows omission, but an empty value causes HTTP 400 and the SDK returns the original reply without an ad. Use your bot’s selected language when Telegram omits it. Telegram User docs → |
isPremium | bool? | Telegram Premium status. SDK sends false if null/None. Telegram User docs → |
keyboard | InlineKeyboardMarkup? | Your existing inline keyboard. Python merges aiogram/PTB objects; plain dicts are returned unchanged. Node.js accepts an inline_keyboard object. |
parseMode | string? | "HTML", "MarkdownV2", or "Markdown". Server escapes and formats the ad CTA for the given parse mode. |
adButtonPosition | "bottom" | "top" | Where to add the ad button row. Default: "bottom". |
firstName | string? | Optional Telegram first name for audience profiling. Up to 128 characters. |
userMessage | string? | User message for context matching, sensitive-topic checks and audience profiling. Up to 4096 characters. |
acceptFormats | string[]? | Formats the bot accepts: "text", "response", "card", "consent", "quiz". Default ["text"]. See Rich formats. |
capabilities | object? | Rendering capabilities. Python uses rich_messages, callbacks, custom_emoji; Node.js uses richMessages, callbacks, customEmoji. Missing flags are false. |
botIsAdmin | bool? | Whether the bot is a group admin. SDK 0.8.0 does not send group_id, so group-aware delivery requires HTTP. |
Required fields.
Return value
| Field | Type | Description |
|---|---|---|
message | string | Ready-to-send text — original or with ad CTA appended. |
hasAd | bool | Whether an ad was injected. |
impressionId | string? | Impression ID for tracking. |
ad | object? | Ad with text, formatted_text, button_text and button_url. Python returns a dict (ad["text"]); Node.js returns an object (ad.text) with camelCase keys. |
ad.formattedText | string | Formatted ad copy rendered into your parse_mode. For tracked campaigns, authored links are replaced with the click redirect. Without authored formatting, it equals ad.text. |
format | string? | Rich format name at result.format. May be absent (Node.js) or None (Python) for a text ad. Check result.send to decide whether to send a rich scene. |
send | object? | Rich formats only: result.send contains { method, params, ephemeral? }. The HTTP response uses ad.send. Send the original bot reply separately when sending a rich scene. |
interactToken | string? | Compatibility field. The current API does not return it separately; interactive tokens are embedded in sk:* callback_data. |
keyboard | InlineKeyboardMarkup? | Merged keyboard (your buttons + ad button), or original if no ad. |
fetch()
Fetch an ad object without sending your message text. Returns the ad as separate fields that you render into your bot's reply yourself. Async, never throws — on any error returns { hasAd: false } (or { has_ad: False } in Python).
import { Sidekick } from "sidekick-sdk";const sk = new Sidekick({ apiKey: "sk_live_xxx", platformId: "plt_xxx" });const result = await sk.fetch({userId: ctx.from.id,languageCode: ctx.from.language_code || "en",parseMode: "HTML", // Match the parse mode used when sending.});if (result.hasAd && result.ad) {await ctx.reply(`${llmReply}\n\n${result.ad.adTextFormatted}`, {parse_mode: "HTML",reply_markup: {inline_keyboard: [[{ text: result.ad.buttonText, url: result.ad.buttonUrl },]],},});} else {await ctx.reply(llmReply);}
Parameters
| Parameter | Type | Description |
|---|---|---|
userIdrequired | number | Finite numeric Telegram user ID (ctx.from.id). Telegram User docs → |
languageCoderequired | string | Non-empty IETF language tag. Use your bot’s selected language when Telegram does not provide language_code. Telegram User docs → |
isPremium | bool? | Telegram Premium status. Default: false. |
parseMode | string? | "HTML", "MarkdownV2", or "Markdown". When set, response includes a pre-formatted ad_text_formatted field. |
acceptFormats | string[]? | Ad formats this caller renders. Default ["text"] (text-only, legacy behavior). Pass ["text", "image"] to receive image ads too — the SDK will pre-download the asset bytes into ad.image_data / ad.imageData so you can hand them straight to InputFile. |
Required fields.
Return value (FetchAnyResult)
| Field | Type | Description |
|---|---|---|
hasAd | bool | Whether an ad was returned. |
impressionId | string? | Impression ID (tracks the impression). Null when has_ad is false. |
ad | object? | Null when has_ad is false. Otherwise an object with the fields below. |
ad.format | "text" | "image" | Returned format. For image ads, the extra media fields are listed under fetch_image() / fetchImage(). |
ad.adText | string | Raw campaign text. |
ad.adUrl | string | Click URL. |
ad.buttonText | string | Inline button label. |
ad.buttonUrl | string | Inline button URL. A tracking redirect or direct destination, depending on the campaign; use it unchanged. |
ad.adTextFormatted | string? | Present only if parse_mode was in the request. Escaped text with the click URL embedded per parse mode. |
fetch() vs inject() — use inject() if you want the SDK to handle message splicing and keyboard merging for you. Use fetch() if your bot serves LLM text that may contain user data (personal names, query fragments) and you don't want to send that text to Sidekick's servers.
fetchImage()
Fetch an image ad (photo or animation) to display separately from your LLM reply. Calls the strict image-only /api/v1/ad/fetch-image endpoint. Async, never throws — on any error returns { hasAd: false }.
import { Sidekick } from "sidekick-sdk";import { InputFile } from "grammy";const sk = new Sidekick({ apiKey: "sk_live_xxxxx", platformId: "plt_xxxxx" });const r = await sk.fetchImage({userId: ctx.from.id,languageCode: "en",parseMode: "HTML",});if (r.hasAd && r.ad) {// SDK already downloaded the bytes — hand straight to InputFile.const media = new InputFile(r.ad.imageData, r.ad.imageFileName);const options = {caption: r.ad.adTextFormatted ?? r.ad.adText,parse_mode: "HTML" as const,reply_markup: {inline_keyboard: [[{ text: r.ad.buttonText, url: r.ad.buttonUrl }]],},};if (r.ad.mediaType === "animation") {await ctx.replyWithAnimation(media, options);} else {await ctx.replyWithPhoto(media, options);}}
Parameters
| Parameter | Type | Description |
|---|---|---|
userIdrequired | number | Finite numeric Telegram user ID (ctx.from.id). Telegram User docs → |
languageCoderequired | string | Non-empty IETF language tag. Use your bot’s selected language when Telegram does not provide language_code. Telegram User docs → |
isPremium | bool? | Telegram Premium status. Default: false. |
parseMode | string? | "HTML", "MarkdownV2", or "Markdown". When set, response includes a pre-formatted ad_text_formatted field — the caption linked to the click URL in the selected parse mode. |
Required fields.
Return value (FetchImageResult)
| Field | Type | Description |
|---|---|---|
hasAd | bool | Whether an image ad was returned. |
impressionId | string? | Impression ID. Null when has_ad is false. |
ad | object? | Null when has_ad is false. Otherwise an object with the fields below. |
ad.format | "image" | Always "image" for this endpoint. |
ad.adText | string | Plain-text caption for the Telegram message. |
ad.adTextFormatted | string? | Present only if parse_mode was in the request. The caption linked to the click URL in the selected parse mode — pass this as the Telegram caption to make the text tappable. |
ad.adUrl | string | Ad URL, identical to button_url. May be a tracking redirect or a direct destination. |
ad.buttonText | string | Inline button label. |
ad.buttonUrl | string | Inline button URL. A tracking redirect or direct destination, depending on the campaign; use it unchanged. |
ad.mediaType | "photo" | "animation" | Telegram media method to use (sendPhoto or sendAnimation). |
ad.imageUrl | string | Direct URL to the image or GIF. You usually don't need to fetch this yourself — see image_data below. |
ad.imageMime | string | MIME type, e.g. "image/jpeg", "image/gif", "video/mp4". |
ad.imageData | Uint8Array | Pre-downloaded asset bytes — hand straight to aiogram BufferedInputFile or grammy InputFile. The SDK fetches these for you so you don't need to download the image yourself, and it works even if Telegram's URL fetcher can't reach the asset host. |
ad.imageFileName | string | Suggested filename derived from the MIME type, e.g. "ad.jpg". |
Error handling
inject() never throws. On any error — timeout, network failure, 5xx, malformed JSON — it returns your original message and keyboard unchanged. Your bot keeps working no matter what.
// On a handled Sidekick request error:const result = await sk.inject({ userId: 123, message: "Hello world", languageCode: "en" });// result.message === "Hello world" (unchanged)// result.hasAd === false// result.keyboard === null (or your original keyboard)
Existing keyboards
- •Ad button is always added as a separate row — never mixed with your buttons.
- •Position controlled by
adButtonPosition:"bottom"(default) or"top". - •Telegram allows max 13 keyboard rows. If you already have 13 — ad button is skipped, text CTA only.
- •Your existing buttons are never modified or removed.
- •If message + ad exceeds 4096 chars, text CTA is dropped but button is still added.