{"components":{"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Missing or invalid parameters"},"Unauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Invalid or missing API key"}},"schemas":{"Channel":{"properties":{"avatar_id":{"description":"Telegram photo ID of the channel avatar","type":["string","null"]},"avatar_url":{"description":"Signed URL to fetch the avatar via /files/avatar/{id}. Treat as one-time capture source, not CDN.","type":["string","null"]},"boosts_applied":{"type":["integer","null"]},"boosts_unrestrict":{"description":"Boosts needed to lift slowmode/restrictions","type":["integer","null"]},"bot_verification":{"description":"Organization-issued verification record (Telegram's 2025 two-tier verification). Set when a bot has attached a custom verification badge to this channel via bots.setCustomVerification. Distinct from Telegram-native `is_verified` (blue check).","properties":{"bot_id":{"description":"Telegram user_id of the bot that issued the verification","type":["string","null"]},"description":{"description":"Reason/description shown to clients in the verification popup","type":["string","null"]},"icon":{"properties":{"document_id":{"description":"Custom emoji document_id of the badge","type":"string"},"url":{"description":"Signed URL to fetch the rendered icon via /files/custom_emoji/{id}","type":"string"}},"type":["object","null"]}},"type":["object","null"]},"bot_verification_icon":{"description":"Lightweight pointer to the organization-issued verification badge (Telegram's 2025 two-tier verification — a bot is allowed to attach a custom-emoji checkmark to this peer). Resolves the icon only; for the full record (issuing bot id + description) call /peers.getInfo or /channels.getInfo, which carry `bot_verification`.","properties":{"document_id":{"description":"Custom emoji document_id of the verification icon","type":"string"},"url":{"description":"Signed URL to fetch the rendered icon via /files/custom_emoji/{id}","type":"string"}},"type":["object","null"]},"creation_date":{"format":"date-time","type":["string","null"]},"description":{"type":["string","null"]},"has_photo":{"type":"boolean"},"id":{"type":"string"},"is_fake":{"type":"boolean"},"is_restricted":{"type":"boolean"},"is_scam":{"type":"boolean"},"is_verified":{"description":"Telegram-native verification (the platform blue checkmark)","type":"boolean"},"level":{"description":"Channel boost level (different from user star level)","type":["integer","null"]},"linked_chat_id":{"type":["string","null"]},"location":{"properties":{"address":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"}},"type":["object","null"]},"online":{"type":["integer","null"]},"paid_media_allowed":{"type":"boolean"},"paid_messages_available":{"type":"boolean"},"paid_reactions_available":{"type":"boolean"},"restrictions":{"items":{"properties":{"platform":{"type":"string"},"reason":{"type":"string"},"text":{"type":"string"}},"type":"object"},"type":["array","null"]},"send_paid_messages_stars":{"description":"Stars charged per message in this peer (paid messages)","type":["integer","null"]},"slowmode_seconds":{"type":["integer","null"]},"stargifts_available":{"type":"boolean"},"stargifts_count":{"type":["integer","null"]},"subscribers":{"type":["integer","null"]},"subscription_until_date":{"format":"date-time","type":["string","null"]},"title":{"type":"string"},"type":{"enum":["channel","supergroup","group"],"type":"string"},"username":{"type":["string","null"]}},"type":"object"},"ErrorResponse":{"properties":{"error":{"properties":{"code":{"type":"string"},"details":{},"message":{"type":"string"}},"type":"object"},"meta":{"$ref":"#/components/schemas/Meta"},"ok":{"const":false,"type":"boolean"}},"type":"object"},"Media":{"additionalProperties":true,"description":"Polymorphic media object — check `type` field to determine shape.\nFor downloadable kinds (`photo`, `video`, `video_note`, `audio`, `voice`, `sticker`, `gif`, `document`) the object also includes:\n- `url` — signed URL to fetch via /files/{kind}/{id}\n- `photo_id` or `document_id` — raw Telegram int64 IDs\n- `mime_type`, `size` — standard document fields\n- `raw` — all raw Telegram TL fields (id, access_hash, file_reference_b64, dc_id, attributes, sizes) for callers that want to download via MTProto directly","properties":{"document_id":{"type":["string","null"]},"mime_type":{"type":["string","null"]},"photo_id":{"type":["string","null"]},"raw":{"description":"Full Telegram TL fields for direct MTProto download: id, access_hash, file_reference_b64, dc_id, mime_type, size, attributes[] (documents) or sizes[] + video_sizes[] (photos).","type":["object","null"]},"size":{"type":["integer","null"]},"type":{"enum":["photo","video","video_note","audio","voice","sticker","gif","document","webpage","poll","location","live_location","contact","giveaway","giveaway_results","invoice","dice","venue","game","story","paid_media","unknown"],"type":"string"},"url":{"description":"Signed gramesh file URL (downloadable kinds only)","type":["string","null"]}},"type":["object","null"]},"Meta":{"properties":{"account_used":{"description":"Masked phone of the account used","type":"string"},"cache":{"properties":{"hit":{"type":"boolean"},"ttl":{"type":"integer"}},"type":"object"},"pagination":{"properties":{"count":{"type":"integer"},"next_cursor":{"type":["integer","string","null"]}},"type":"object"},"request_id":{"type":"string"},"timestamp":{"format":"date-time","type":"string"}},"type":"object"},"Post":{"properties":{"date":{"format":"date-time","type":["string","null"]},"forwards":{"type":["integer","null"]},"grouped_id":{"type":["string","null"]},"id":{"type":"integer"},"media":{"$ref":"#/components/schemas/Media"},"reactions":{"items":{"properties":{"count":{"type":"integer"},"emoji":{"type":"string"}},"type":"object"},"type":["array","null"]},"replies":{"properties":{"count":{"type":"integer"},"recent_posters":{"type":"integer"}},"type":["object","null"]},"text":{"description":"Truncated to 300 chars in list view","type":["string","null"]},"text_truncated":{"type":"boolean"},"type":{"enum":["message","service"],"type":"string"},"views":{"type":["integer","null"]}},"type":"object"},"PostFull":{"allOf":[{"$ref":"#/components/schemas/Post"},{"properties":{"edit_date":{"format":"date-time","type":["string","null"]},"entities":{"items":{"properties":{"document_id":{"description":"For customemoji entities — points to a Telegram custom emoji document","type":"string"},"file_url":{"description":"For customemoji entities — signed URL to the rendered emoji (TGS/WEBM/WEBP). May be `application/x-tgsticker` which needs a TGS player on the client.","type":"string"},"language":{"description":"For pre entities","type":"string"},"length":{"type":"integer"},"offset":{"type":"integer"},"type":{"description":"Entity type (bold, italic, customemoji, …)","type":"string"},"url":{"description":"For texturl entities","type":"string"},"user_id":{"description":"For mentionname entities","type":"string"}},"type":"object"},"type":"array"},"forward":{"properties":{"date":{"format":"date-time","type":["string","null"]},"from_channel_id":{"type":["string","null"]},"from_message_id":{"type":["integer","null"]},"from_name":{"type":["string","null"]}},"type":["object","null"]},"is_pinned":{"type":"boolean"},"is_silent":{"type":"boolean"},"text":{"description":"Full text, not truncated","type":["string","null"]}},"type":"object"}],"description":"Full post (returned by channels.getMessage) — includes all Post fields plus additional detail"},"SavedStarGift":{"description":"A gift that the recipient has chosen to display on their profile. `pinned_to_top=true` for pinned gifts.","properties":{"collection_ids":{"items":{"type":"integer"},"type":["array","null"]},"date":{"format":"date-time","type":["string","null"]},"from":{"description":"Sender peer (null when name_hidden)","type":["object","null"]},"gift":{"$ref":"#/components/schemas/StarGiftBase"},"gift_num":{"type":["integer","null"]},"message":{"description":"TextWithEntities attached to the gift","type":["object","null"]},"msg_id":{"description":"Message ID where the gift was originally sent (users only)","type":["integer","null"]},"name_hidden":{"description":"Sender opted to be anonymous","type":"boolean"},"pinned_to_top":{"type":"boolean"},"saved_id":{"description":"Identifier for follow-up payments.GetSavedStarGift calls (chats only)","type":["string","null"]}},"type":"object"},"SearchChannel":{"properties":{"bot_verification_icon":{"description":"Organization-issued verification icon (custom emoji). See Channel.bot_verification_icon.","properties":{"document_id":{"type":"string"},"url":{"type":"string"}},"type":["object","null"]},"id":{"type":"string"},"is_verified":{"description":"Telegram-native blue checkmark","type":"boolean"},"subscribers":{"type":["integer","null"]},"title":{"type":"string"},"type":{"enum":["channel","supergroup","group"],"type":"string"},"username":{"type":["string","null"]}},"type":"object"},"SponsoredMessage":{"properties":{"additional_info":{"type":["string","null"]},"button_text":{"type":["string","null"]},"can_report":{"type":"boolean"},"color":{"properties":{"background_emoji_id":{"type":["string","null"]},"color":{"type":["integer","null"]}},"type":["object","null"]},"entities":{"items":{"properties":{"length":{"type":"integer"},"offset":{"type":"integer"},"type":{"type":"string"},"url":{"type":"string"},"user_id":{"type":"string"}},"type":"object"},"type":["array","null"]},"max_display_duration":{"type":["integer","null"]},"media":{"$ref":"#/components/schemas/Media"},"message":{"description":"Ad copy (plain text)","type":["string","null"]},"min_display_duration":{"type":["integer","null"]},"photo":{"description":"Sponsor logo/preview photo. Eagerly cached by gramesh at response time so `url` works even after Telegram's file_reference expires.","properties":{"dc_id":{"type":["integer","null"]},"height":{"type":["integer","null"]},"photo_id":{"type":["string","null"]},"raw":{"description":"Raw Telegram Photo fields (id, access_hash, file_reference_b64, sizes[], video_sizes[]) for callers that want to do their own downloading via MTProto","type":["object","null"]},"url":{"description":"Signed URL to fetch the photo via /files/photo/{id}","type":["string","null"]},"width":{"type":["integer","null"]}},"type":["object","null"]},"random_id":{"description":"Hex-encoded impression token from Telegram","type":["string","null"]},"recommended":{"description":"Telegram-recommended content rather than paid ad","type":"boolean"},"sponsor_info":{"description":"Advertiser disclosure string","type":["string","null"]},"title":{"type":["string","null"]},"url":{"description":"Target URL of the ad","type":["string","null"]}},"type":"object"},"StarGiftBase":{"additionalProperties":true,"description":"Star gift. Discriminated by `kind`: `regular` (catalogue gift) or `unique` (upgraded one-of-a-kind). Regular and unique carry different field sets (below); `additionalProperties` stays open for forward-compat.","properties":{"attributes":{"description":"unique: gift attributes, discriminated by `type`.","items":{"description":"model|pattern: {type,name,crafted,rarity_permille,document_id,url,mime_type,raw}. backdrop: {type,name,backdrop_id,center_color,edge_color,pattern_color,text_color,rarity_permille}. original_details: {type,sender,recipient,date,message}.","properties":{"backdrop_id":{"type":["integer","null"]},"center_color":{"type":["integer","null"]},"crafted":{"type":"boolean"},"date":{"type":["string","null"]},"document_id":{"type":["string","null"]},"edge_color":{"type":["integer","null"]},"message":{"type":["object","null"]},"mime_type":{"type":["string","null"]},"name":{"type":["string","null"]},"pattern_color":{"type":["integer","null"]},"rarity_permille":{"type":["integer","null"]},"raw":{"type":["object","null"]},"recipient":{"description":"Peer reference {type:user|channel|chat, id}","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]}},"type":["object","null"]},"sender":{"description":"Peer reference {type:user|channel|chat, id}","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]}},"type":["object","null"]},"text_color":{"type":["integer","null"]},"type":{"enum":["model","pattern","backdrop","original_details"],"type":"string"},"url":{"type":["string","null"]}},"type":"object"},"type":["array","null"]},"availability_issued":{"description":"unique.","type":["integer","null"]},"availability_remains":{"description":"regular.","type":["integer","null"]},"availability_resale":{"description":"regular.","type":["integer","null"]},"availability_total":{"description":"regular/unique.","type":["integer","null"]},"background":{"description":"regular: gift background colors.","properties":{"center_color":{"type":["integer","null"]},"edge_color":{"type":["integer","null"]},"pattern_color":{"type":["integer","null"]},"text_color":{"type":["integer","null"]}},"type":["object","null"]},"convert_stars":{"description":"regular: Stars returned if converted.","type":["integer","null"]},"craft_chance_permille":{"description":"unique.","type":["integer","null"]},"first_sale_date":{"description":"regular: ISO date.","type":["string","null"]},"flags":{"description":"Boolean flags. Regular set: limited/sold_out/birthday/require_premium/limited_per_user/peer_color_available/auction. Unique set: require_premium/resale_ton_only/theme_available/burned/crafted.","properties":{"auction":{"type":"boolean"},"birthday":{"type":"boolean"},"burned":{"type":"boolean"},"crafted":{"type":"boolean"},"limited":{"type":"boolean"},"limited_per_user":{"type":"boolean"},"peer_color_available":{"type":"boolean"},"require_premium":{"type":"boolean"},"resale_ton_only":{"type":"boolean"},"sold_out":{"type":"boolean"},"theme_available":{"type":"boolean"}},"type":["object","null"]},"gift_address":{"description":"unique: TON address.","type":["string","null"]},"gift_id":{"description":"unique: catalogue gift id this was upgraded from.","type":["string","null"]},"host":{"description":"Peer reference {type:user|channel|chat, id}","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]}},"type":["object","null"]},"id":{"description":"Gift id.","type":["string","null"]},"kind":{"description":"Gift variant discriminator.","enum":["regular","unique"],"type":"string"},"last_sale_date":{"description":"regular: ISO date.","type":["string","null"]},"locked_until_date":{"description":"regular: ISO date.","type":["string","null"]},"num":{"description":"unique: sequential number.","type":["integer","null"]},"offer_min_stars":{"description":"unique.","type":["integer","null"]},"owner":{"description":"Peer reference {type:user|channel|chat, id}","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]}},"type":["object","null"]},"owner_address":{"description":"unique: TON address.","type":["string","null"]},"owner_name":{"description":"unique.","type":["string","null"]},"peer_color":{"description":"unique.","properties":{"background_emoji_id":{"type":["string","null"]},"color":{"type":["integer","null"]}},"type":["object","null"]},"per_user_remains":{"description":"regular.","type":["integer","null"]},"per_user_total":{"description":"regular.","type":["integer","null"]},"released_by":{"description":"Peer reference {type:user|channel|chat, id}","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]}},"type":["object","null"]},"resell_amount":{"description":"unique: resale price components.","items":{"properties":{"amount":{"type":["integer","null"]},"nanos":{"type":["integer","null"]}},"type":["object","null"]},"type":["array","null"]},"resell_min_stars":{"description":"regular: minimum resale price in Stars.","type":["integer","null"]},"slug":{"description":"unique: URL slug.","type":["string","null"]},"stars":{"description":"regular: price in Stars.","type":["integer","null"]},"sticker":{"properties":{"document_id":{"type":["string","null"]},"mime_type":{"type":["string","null"]},"raw":{"description":"Full document TL (file_reference_b64, attributes[], …)","type":["object","null"]},"size":{"type":["integer","null"]},"url":{"type":["string","null"]}},"type":["object","null"]},"theme_peer":{"description":"Peer reference {type:user|channel|chat, id}","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]}},"type":["object","null"]},"title":{"description":"Gift title/name.","type":["string","null"]},"upgrade_stars":{"description":"regular: Stars cost to upgrade.","type":["integer","null"]},"upgrade_variants":{"description":"regular.","type":["integer","null"]},"value_amount":{"description":"unique.","type":["integer","null"]},"value_currency":{"description":"unique.","type":["string","null"]},"value_usd_amount":{"description":"unique.","type":["integer","null"]}},"type":"object"},"SuccessResponse":{"properties":{"data":{},"meta":{"$ref":"#/components/schemas/Meta"},"ok":{"const":true,"type":"boolean"}},"type":"object"},"User":{"properties":{"avatar_id":{"type":["string","null"]},"avatar_url":{"description":"Signed URL to the profile avatar. Treat as one-time capture source, not CDN.","type":["string","null"]},"bio":{"description":"Bio / about text from users.GetFullUser. Only present when the call returned UserFull (peers.getInfo, users.getInfo).","type":["string","null"]},"birthday":{"properties":{"day":{"type":"integer"},"month":{"type":"integer"},"year":{"type":["integer","null"]}},"type":["object","null"]},"bot_active_users":{"description":"Approximate monthly active users of the bot","type":["integer","null"]},"bot_attach_menu":{"description":"Bot can be used in attach menu","type":"boolean"},"bot_business":{"description":"Bot supports Telegram Business","type":"boolean"},"bot_chat_history":{"description":"Bot has access to chat history","type":"boolean"},"bot_has_main_app":{"description":"Bot has a main mini app","type":"boolean"},"bot_info_version":{"type":["integer","null"]},"bot_inline_geo":{"description":"Bot requests geolocation for inline queries","type":"boolean"},"bot_inline_placeholder":{"type":["string","null"]},"bot_nochats":{"description":"Bot cannot be added to groups","type":"boolean"},"bot_verification":{"description":"Organization-issued verification record (Telegram's 2025 two-tier verification). Returned for bots and users that have been verified by a bot via bots.setCustomVerification. Distinct from `is_verified` (Telegram-native blue check). Only returned when the call hit UserFull (peers.getInfo, users.getInfo).","properties":{"bot_id":{"description":"Telegram user_id of the bot that issued the verification","type":["string","null"]},"description":{"description":"Reason/description shown in the verification popup","type":["string","null"]},"icon":{"properties":{"document_id":{"description":"Custom emoji document_id of the badge","type":"string"},"url":{"description":"Signed URL to fetch the rendered icon via /files/custom_emoji/{id}","type":"string"}},"type":["object","null"]}},"type":["object","null"]},"bot_verification_icon":{"description":"Lightweight pointer to the organization-issued verification badge (Telegram's 2025 two-tier verification). A bot has attached a custom-emoji checkmark to this user/bot. For the full record (issuing bot id + description) see `bot_verification` (only returned by /peers.getInfo and /users.getInfo, which carry the UserFull data).","properties":{"document_id":{"description":"Custom emoji document_id of the verification icon","type":"string"},"url":{"description":"Signed URL to fetch the rendered icon via /files/custom_emoji/{id}","type":"string"}},"type":["object","null"]},"business_intro":{"properties":{"description":{"type":["string","null"]},"sticker":{"description":"Greeting sticker document","type":["object","null"]},"title":{"type":["string","null"]}},"type":["object","null"]},"business_location":{"properties":{"address":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]}},"type":["object","null"]},"business_work_hours":{"properties":{"open_now":{"type":"boolean"},"timezone_id":{"type":["string","null"]},"weekly_open":{"items":{"properties":{"end_minute":{"type":"integer"},"start_minute":{"description":"Minute of the week (0–10079)","type":"integer"}},"type":"object"},"type":"array"}},"type":["object","null"]},"color":{"description":"Name/reply color customization","properties":{"background_emoji_id":{"type":["string","null"]},"color":{"type":["integer","null"]}},"type":["object","null"]},"common_chats_count":{"description":"Common chats with the requesting pool account (perspective-relative — semi-public)","type":["integer","null"]},"contact_require_premium":{"description":"User requires premium to be contacted","type":"boolean"},"dc_id":{"description":"Telegram datacenter ID where user data is stored","type":["integer","null"]},"disallowed_gifts":{"description":"Which gift types the user has disabled receiving","properties":{"disallow_from_channels":{"type":"boolean"},"disallow_limited":{"type":"boolean"},"disallow_premium":{"type":"boolean"},"disallow_unique":{"type":"boolean"},"disallow_unlimited":{"type":"boolean"}},"type":["object","null"]},"display_gifts_button":{"type":"boolean"},"emoji_status":{"description":"Custom emoji status set by the user","properties":{"document_id":{"type":"string"},"until":{"description":"Unix timestamp when the status expires","type":["integer","null"]}},"type":["object","null"]},"fallback_photo":{"description":"Fallback photo shown to peers who can't see the real one","type":["object","null"]},"first_name":{"type":["string","null"]},"has_photo":{"type":"boolean"},"has_video_photo":{"description":"Profile photo is an animated/video avatar","type":"boolean"},"id":{"type":"string"},"is_bot":{"type":"boolean"},"is_close_friend":{"type":"boolean"},"is_contact":{"type":"boolean"},"is_deleted":{"type":"boolean"},"is_fake":{"type":"boolean"},"is_mutual_contact":{"type":"boolean"},"is_premium":{"type":"boolean"},"is_restricted":{"type":"boolean"},"is_scam":{"type":"boolean"},"is_self":{"description":"True if this is the pool account itself","type":"boolean"},"is_support":{"description":"Telegram support account","type":"boolean"},"is_verified":{"description":"Telegram-native verification (the platform blue checkmark)","type":"boolean"},"lang_code":{"description":"IETF language code (e.g. en, ru)","type":["string","null"]},"last_name":{"type":["string","null"]},"main_tab":{"description":"Default profile tab the user has set","enum":["posts","gifts","media","files","music","voice","links","gifs",null],"type":["string","null"]},"personal_channel_id":{"type":["string","null"]},"personal_channel_message_id":{"type":["integer","null"]},"phone":{"description":"Only visible if the user shares it","type":["string","null"]},"phone_calls_available":{"type":"boolean"},"photo_dc_id":{"description":"DC where the photo is stored","type":["integer","null"]},"photo_id":{"description":"Profile photo file ID","type":["string","null"]},"profile_color":{"description":"Profile page color customization","properties":{"background_emoji_id":{"type":["string","null"]},"color":{"type":["integer","null"]}},"type":["object","null"]},"profile_photo":{"description":"Public profile photo (UserFull.profile_photo)","properties":{"dc_id":{"type":["integer","null"]},"has_video":{"type":"boolean"},"height":{"type":["integer","null"]},"photo_id":{"type":"string"},"raw":{"type":["object","null"]},"url":{"type":"string"},"width":{"type":["integer","null"]}},"type":["object","null"]},"read_dates_private":{"type":"boolean"},"restriction_reason":{"items":{"properties":{"platform":{"type":"string"},"reason":{"type":"string"},"text":{"type":"string"}},"type":"object"},"type":["array","null"]},"saved_music":{"description":"Pinned song on the profile (the first song on the music tab). Gated by user privacy — null if hidden.","properties":{"document_id":{"type":"string"},"duration":{"type":["integer","null"]},"mime_type":{"type":["string","null"]},"performer":{"type":["string","null"]},"raw":{"type":["object","null"]},"size":{"type":["integer","null"]},"title":{"type":["string","null"]},"url":{"description":"Signed URL to fetch the audio via /files/document/{id}. Bytes are eagerly cached server-side at /users.getInfo / /peers.getInfo response time, so the URL keeps working after Telegram's file_reference expires.","type":"string"}},"type":["object","null"]},"send_paid_messages_stars":{"description":"Stars per message required to DM this user","type":["integer","null"]},"sponsored_enabled":{"type":"boolean"},"stargifts_count":{"description":"Number of saved gifts the user displays on profile. Use /users.getGifts to enumerate them.","type":["integer","null"]},"stars_rating":{"description":"Public Telegram Stars rating (the level badge on the profile). Increases with star purchases / paid messages / suggested-post funding; decreases with refunds / gift conversions. `level` may be negative.","properties":{"current_level_stars":{"description":"Stars threshold of the current level","type":"integer"},"level":{"type":"integer"},"next_level_stars":{"description":"Stars threshold of the next level (null at the cap)","type":["integer","null"]},"stars":{"description":"Total accumulated stars in rating","type":"integer"}},"type":["object","null"]},"status":{"enum":["online","offline","recently","last_week","last_month",null],"type":["string","null"]},"status_expires":{"description":"When online status expires (only when status is online)","format":"date-time","type":["string","null"]},"status_last_online":{"description":"Exact last online timestamp (only when status is offline)","format":"date-time","type":["string","null"]},"stories_hidden":{"type":"boolean"},"stories_max_id":{"type":["integer","null"]},"stories_pinned_available":{"type":"boolean"},"stories_unavailable":{"type":"boolean"},"username":{"description":"Primary username","type":["string","null"]},"usernames":{"description":"All collectible/fragment usernames","items":{"type":"string"},"type":["array","null"]},"video_calls_available":{"type":"boolean"},"voice_messages_forbidden":{"type":"boolean"}},"type":"object"}},"securitySchemes":{"apiKey":{"in":"header","name":"X-Api-Key","type":"apiKey"},"bearerAuth":{"scheme":"bearer","type":"http"}}},"info":{"description":"# Source of truth\n\n**This spec, served at `GET /docs`, is the authoritative reference.** If external docs, code comments, or AI models disagree with it, trust this spec. Re-fetch `/docs` when in doubt.\n\n# This is NOT a scraping service\n\nGramesh is a structured interface to a pool of real Telegram accounts, each subject to Telegram's rate limits and anti-abuse systems. Every API call becomes real MTProto requests from real accounts.\n\n- Do not poll endpoints in tight loops — Telegram rate-limits per account, surfaced as `FLOOD_WAIT`.\n- A bare numeric user ID resolves **only** if the pool has already encountered that user (see \"Entity resolution model\").\n- Gramesh does **not** join chats, import invites, or send join requests. Private chats are accessible only if a pool account is already a member.\n- Channel info and posts are cached (5 min TTL). User data is whatever the pool collected as a side-effect of prior calls — there is no background collection and no real-time update handler.\n- Handle errors gracefully: `NOT_FOUND`, `FLOOD_WAIT`, `NO_HEALTHY_ACCOUNTS`, `UPSTREAM_ERROR` are normal operational states.\n\n# Architecture\n\nGramesh manages a bounded, rotating pool of accounts. Each holds a persistent MTProto session and executes requests on demand. Entities (users, channels) returned by a request are persisted to a cache, each user with its per-account `access_hash`. This request-driven harvesting is the **only** way users enter the cache — it happens on the responses to calls you make, never in the background.\n\n# Entity resolution model\n\n## Channels & groups\n\n`target` accepts: `@username` or bare `username`, `t.me/username`, or a raw numeric channel ID. Public targets are resolved live via `contacts.resolveUsername` (or from cache if seen before), then full metadata is fetched. Invite-hash links (`t.me/+hash`, `joinchat`) and private `t.me/c/...` links are **not** supported.\n\n## Users\n\n- A `@username` target is resolved **live** via `contacts.resolveUsername`.\n- A **bare numeric user ID** is resolved **only from the cache** — MTProto needs a per-account `access_hash`, so there is no by-ID lookup call. An unseen id returns `UPSTREAM_ERROR` (`PEER_ID_INVALID`).\n\nUsers enter the cache as a side-effect of `/channels.getHistory`, `/channels.getInfo`, `/users.getInfo`, gifts (the `users[]` Telegram returns with them), and `/contacts.search` / `/search.query`.\n\nIf a numeric user hasn't been seen, pass the `chat` parameter (a `@username`, `t.me/username`, or numeric channel ID where the user is active). The pool reads that chat's latest ~50 messages and harvests their users, then retries — **no joining**, just a one-shot history scan. Message-anchor (`/123`), `?comment=`, and `t.me/c/...` forms are not supported for `chat`.\n\n# Rate limits & operational constraints\n\n- **FLOOD_WAIT:** the flooded account is cooled down and the request rotates to another, within a retry budget. If none can serve in time → `FLOOD_WAIT` (HTTP 429, `details.retry_after`).\n- **Account health:** the pool rotates accounts and persistently marks dead ones; permanently banned accounts are dropped.\n- **Cache TTL:** channel info is cached 5 minutes (cache hits carry `meta.cache`).\n- **region:** a best-effort soft hint, echoed where applicable; it does **not** currently pin account selection to a country.\n- **No coverage guarantees:** resolvability depends on what the pool has seen.\n\n# Errors\n\n`{ok:false, error:{code,message,details?}, meta}`. Codes:\n- `NOT_FOUND` (404) — username doesn't exist / unseen.\n- `FLOOD_WAIT` (429, `details.retry_after`) — rate-limited.\n- `NO_HEALTHY_ACCOUNTS` (503) — no account could serve within the retry budget.\n- `IS_USER` / `NOT_A_BOT` / `NOT_A_USER` (400) — wrong peer type for this endpoint.\n- `UPSTREAM_ERROR` (502) — other Telegram-side failure (e.g. unseen numeric id).\n- `UNAUTHORIZED` (401) — missing/invalid API key.\n\n# Examples\n\n- **Channel info:** `POST /channels.getInfo {\"target\":\"durov\"}` → metadata; channel + response users cached.\n- **Posts:** `POST /channels.getHistory {\"target\":\"durov\",\"limit\":50}` → paginated posts (`meta.pagination`); page back with `offset_id`.\n- **Find a user ID:** `POST /contacts.search {\"q\":\"john\"}`.\n- **User by ID:** `POST /users.getInfo {\"target\":\"123456789\"}` → profile **if** seen, else `UPSTREAM_ERROR`. Seed first, or pass `chat`.\n- **User via chat context (one call):** `POST /users.getInfo {\"target\":\"123456789\",\"chat\":\"somegroup\"}` → scans the chat's latest ~50 messages to seed, then resolves.\n\n# Integration guide\n\nThe pool is a request-driven cache, not an observer. To look up a user by bare numeric ID, seed the cache first:\n1. **Seed** — call `/channels.getHistory` (high `limit`) or `/contacts.search` where the user is active; the response's users are cached. (Or pass `chat` to `/users.getInfo` to do both in one call.)\n2. **Query** — `/users.getInfo` by numeric ID now succeeds.\n\nThe pool **can** serve users Telegram returns in responses to getHistory/getInfo/getFullUser/gifts and contacts.search. It **cannot** serve users no account has encountered. A miss is not evidence of anything — don't infer \"suspicious\" from absence.\n\n# Common mistakes\n\n- Polling getHistory in a loop → `FLOOD_WAIT`. Cache results; re-query only when needed.\n- `users.getInfo` with an unseen numeric id → `UPSTREAM_ERROR` (502). Seed via getHistory/search or pass `chat`.\n- Expecting any arbitrary user to resolve — coverage is limited to what the pool has seen, by design.","title":"Gramesh API","version":"0.3.0"},"openapi":"3.1.0","paths":{"/bots.getPopular":{"post":{"description":"Returns the global popular-mini-apps list shown in Telegram's official clients under the **Apps** tab in global search. Backed by `bots.GetPopularAppBots`.\n\nEach item is a full bot `User` object — same shape as `/users.getInfo` returns — including `username`, `first_name`, avatar URL, `is_bot: true`, verification flags, etc. All returned users are passively collected into the entity cache, so a follow-up `/users.getInfo` for any of them will work.\n\n**What this list IS:**\n- A flat, paginated stream of bots that operate mini-apps and rank as popular in Telegram's curation.\n\n**What this list IS NOT:**\n- Not split by category (games / finance / earnings / etc.) — MTProto does not expose categories. The category strips you see in the Telegram UI are client-side or come from outside MTProto.\n- No revenue / MAU / star-rating metrics — those fields are not in the schema.\n- No \"top grossing\" variant — there is only one popular-apps endpoint upstream.\n\n**Pagination:** the list is opaque-cursor paginated. On the first call leave `offset` empty (or omit). To fetch the next page, pass back the `meta.pagination.next_cursor` from the previous response. When `next_cursor` is `null`, the list is exhausted.\n\n**Personalization:** results are mildly personalized by the viewing pool account (region, language). The endpoint does not pin a region — it picks any healthy account. If you need country-specific popularity, this endpoint cannot guarantee it; open an issue if region-pinning is needed.","operationId":"botsGetPopular","requestBody":{"content":{"application/json":{"schema":{"properties":{"limit":{"default":50,"description":"default 50, clamped to 1–100.","maximum":100,"minimum":1,"type":"integer"},"offset":{"default":"","description":"Opaque pagination cursor from a previous response's `meta.pagination.next_cursor`. Empty string / omit for the first page.","type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/User"},"type":"array"}},"type":"object"}]}}},"description":"Page of popular mini-app bots. `meta.pagination.next_cursor` is the opaque string to pass as `offset` for the next page (or `null` at end)."},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"FLOOD_WAIT memoized for this method"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"No active accounts"}},"summary":"Global list of popular Telegram mini-apps (bots with web apps)"}},"/bots.getSimilar":{"post":{"description":"Returns Telegram's own bot recommendations for a given bot (the \"similar bots\" block in the bot info sheet in official clients).\n\nBacked by `bots.GetBotRecommendations`. Fails with `NOT_A_BOT` if `target` resolves to a non-bot user. Like channel recommendations, results are personalized per viewing account.","operationId":"botsGetSimilar","requestBody":{"content":{"application/json":{"schema":{"properties":{"target":{"description":"Bot username (@botname) or numeric user ID","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"bots":{"items":{"$ref":"#/components/schemas/User"},"type":"array"},"count":{"type":"integer"},"target":{"type":"string"}},"type":"object"}},"type":"object"}]}}},"description":"Similar bots"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Target is not a bot, or other validation error"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Get bots similar/recommended to the given bot"}},"/bots.getSponsored":{"post":{"description":"Returns the Telegram-native sponsored messages shown as a banner **above a bot's chat**, delivered via `messages.GetSponsoredMessages` with the bot as the peer (`InputPeerUser`).\n\n**Target:** a bot (resolved as a user). `/start` is **not** required — per Telegram's contract the ad request fires when the dialog is opened, regardless of interaction history. Passing a channel returns `NOT_A_BOT`.\n\n**Important caveats:**\n- Ads are **personalized per viewing account** (geo country, language, interests). The same bot returns different creatives depending on which pool account is used.\n- Telegram Premium accounts do **not** see sponsored messages — if the resolving account is Premium, the list is empty.\n- The response is **not cached** server-side; each call hits Telegram and creatives rotate frequently.\n\n**Response:** `bot_id` (resolved user id) and `messages` (ad creatives). Channel-only pacing fields (`posts_between`, `start_delay`, `between_delay`) are **not** returned — they don't apply to bot banners. Per-message fields are described in the `SponsoredMessage` schema.","operationId":"botsGetSponsored","requestBody":{"content":{"application/json":{"schema":{"properties":{"target":{"description":"Bot username (@botname) or numeric user ID","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"bot_id":{"description":"Resolved numeric user id of the bot","type":"string"},"messages":{"description":"Sponsored ad creatives shown above the bot chat. Empty array when the bot currently serves no ads (or the resolving account is Premium).","items":{"$ref":"#/components/schemas/SponsoredMessage"},"type":"array"}},"type":"object"}},"type":"object"}]}}},"description":"Sponsored messages currently served above this bot's chat"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Target is not a bot, or other validation error"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Fetch official Telegram sponsored messages (ads) shown above a bot chat"}},"/channels.getHistory":{"post":{"description":"Returns recent messages from a channel or group.\nAll user entities found in messages (authors, forwarders, mentioned users) are passively collected into the entity cache.\nSupports pagination via `offset_id`.","operationId":"channelsGetHistory","requestBody":{"content":{"application/json":{"schema":{"properties":{"limit":{"default":20,"description":"default 20, clamped to 1–100.","maximum":100,"minimum":1,"type":"integer"},"offset_id":{"description":"Return posts older than this message ID (for pagination)","type":"integer"},"target":{"description":"Channel/group identifier (same formats as channels.getInfo)","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Post"},"type":"array"}},"type":"object"}]}}},"description":"Paginated list of posts"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Get channel post history"}},"/channels.getInfo":{"post":{"description":"Resolves and returns full metadata for a Telegram channel or group via `channels.GetFullChannel`. Cached per channel (5 min TTL); users in the response are harvested into the entity cache.\n\n**Target formats:** `@username` or bare `username`, `t.me/username`, raw numeric channel ID. Invite-hash links (`t.me/+hash`, `joinchat`) and private `t.me/c/...` links are not supported — Gramesh does not import invites or join. Passing a user/bot returns `IS_USER`.","operationId":"channelsGetInfo","requestBody":{"content":{"application/json":{"schema":{"properties":{"target":{"description":"Channel/group identifier: @username, t.me/ link, invite link (t.me/+hash), t.me/c/id private link, or raw numeric ID","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"$ref":"#/components/schemas/Channel"}},"type":"object"}]}}},"description":"Channel info"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Get full channel/group info"}},"/channels.getMessage":{"post":{"operationId":"channelsGetMessage","requestBody":{"content":{"application/json":{"schema":{"properties":{"id":{"description":"Message ID","type":"integer"},"target":{"description":"Channel/group identifier (same formats as channels.getInfo)","type":"string"}},"required":["target","id"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"$ref":"#/components/schemas/PostFull"}},"type":"object"}]}}},"description":"Full post with entities"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Post not found"}},"summary":"Get a single post by ID"}},"/channels.getSimilar":{"post":{"description":"Returns Telegram's own channel recommendations for a given channel (the \"similar channels\" block shown in official clients).\n\nBacked by `channels.GetChannelRecommendations`. Results are **personalized per viewing account** — the same channel can produce slightly different recommendations depending on which pool account is used (region, language, subscription history).\n\nUse to discover adjacent channels for content aggregation, competitor analysis, or mapping topical clusters.\n\n**Response includes** id, title, username, type, subscribers count, verification flags, and an `avatar_url` signed link to the channel photo. The source hint for each avatar is auto-recorded, so the avatar URL resolves on first fetch.","operationId":"channelsGetSimilar","requestBody":{"content":{"application/json":{"schema":{"properties":{"target":{"description":"Channel identifier (same formats as channels.getInfo)","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"channels":{"items":{"$ref":"#/components/schemas/SearchChannel"},"type":"array"},"count":{"description":"Total recommended channels available (may exceed returned array length on Premium-only tails)","type":"integer"},"target":{"type":"string"}},"type":"object"}},"type":"object"}]}}},"description":"Similar channels"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Get channels similar/recommended to the given channel"}},"/channels.getSponsored":{"post":{"description":"Returns the live set of Telegram-native sponsored messages that the pool account currently sees when viewing the target **channel**.\nThese are **official Telegram ads** delivered via `messages.GetSponsoredMessages` — not scraped post content.\n\n**Target:** broadcast channel only (ads are shown between posts; a channel typically needs 1000+ subscribers to serve ads). Passing a bot or user returns `IS_USER` — for **bot ads use `bots.getSponsored`**.\n\n**Important caveats:**\n- Ads are **personalized per viewing account** (geo country, language, interests). The same target returns different creatives depending on which pool account is used.\n- Telegram Premium accounts do **not** see sponsored messages. If the resolving account is Premium, the list will be empty.\n- Creatives rotate frequently. The response is **not cached** server-side — each call hits Telegram.\n\n**Fields:**\n- `random_id` (hex) — opaque ad impression token.\n- `url`, `title`, `message`, `button_text` — the ad creative.\n- `sponsor_info`, `additional_info` — optional advertiser disclosure strings.\n- `recommended` — Telegram flagged this as a recommendation rather than paid ad.\n- `posts_between`, `start_delay`, `between_delay` — client-side pacing hints for channel ads.","operationId":"channelsGetSponsored","requestBody":{"content":{"application/json":{"schema":{"properties":{"target":{"description":"Channel or bot identifier. Channels: same formats as channels.getInfo. Bots: @botname or numeric user ID.","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"between_delay":{"type":["integer","null"]},"channel_id":{"type":"string"},"messages":{"items":{"$ref":"#/components/schemas/SponsoredMessage"},"type":"array"},"posts_between":{"type":["integer","null"]},"start_delay":{"type":["integer","null"]}},"type":"object"}},"type":"object"}]}}},"description":"Sponsored messages currently served for this channel"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Fetch official Telegram sponsored messages (ads) shown in a channel"}},"/contacts.search":{"post":{"description":"Searches Telegram's global directory. All users found in results are passively collected into the entity cache.","operationId":"contactsSearch","requestBody":{"content":{"application/json":{"schema":{"properties":{"limit":{"default":10,"description":"default 10, clamped to 1–50.","maximum":50,"minimum":1,"type":"integer"},"q":{"description":"Search query","type":"string"}},"required":["q"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"channels":{"items":{"$ref":"#/components/schemas/SearchChannel"},"type":"array"},"query":{"type":"string"},"users":{"items":{"$ref":"#/components/schemas/User"},"type":"array"}},"type":"object"}},"type":"object"}]}}},"description":"Search results"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"No active accounts"}},"summary":"Search for channels and users by query"}},"/docs":{"get":{"operationId":"getDocs","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"This OpenAPI specification"}},"security":[],"summary":"OpenAPI schema (no auth required)"}},"/files/{kind}/{id}":{"get":{"description":"Returns raw bytes of a Telegram-hosted asset, so **non-Telegram environments** (browsers, render pipelines) can use the URL as `<img src>`, `<video src>`, or `fetch()`.\n\n**URLs are HMAC-signed.** Do not construct them yourself — use the `url` / `avatar_url` / `file_url` values returned by other endpoints. A signed URL is valid for 1 hour; after `exp` passes the endpoint returns `403`. Call the parent API again to get fresh URLs.\n\n**This is NOT a CDN.** Media delivery is best-effort. We make no guarantees about latency, throughput, or uptime of this endpoint. First-request latency on cache miss can be several seconds (DC round-trip + TG rate limits). Large files (>5 MB) stream per 512 KB chunk, slower still.\n\n**For any production rendering path, mirror the file to your own storage (S3/R2/CDN) on first fetch and serve users from there.** Treat gramesh file URLs as one-time capture sources, not hot-path URLs.\n\n**Supported kinds:**\n- `custom_emoji` — animated/static Telegram custom emoji documents. Immutable by ID; always resolvable.\n- `photo` — photos from channel posts or sponsored messages. Needs source hint (auto-recorded).\n- `document` — documents/videos/stickers/voice/audio from channel posts.\n- `avatar` — profile photos of users and channels.\n- `thumb` — thumbnail of a document.\n\n**Caching:** files ≤ 5 MB are cached in SQLite (`gramesh-blobs.db`). Files > 5 MB proxy-stream on every request.\n\n**MIME types:** standard (`image/jpeg`, `video/mp4`, `audio/ogg`, …). Animated **custom emojis** (`kind=custom_emoji`) are returned as **`application/json`** — already-decompressed Lottie animation JSON, feed straight to a Lottie player, **no client-side gunzip**. Animated **stickers fetched as documents** (`kind=document`) are returned raw as `application/x-tgsticker` (gzipped Lottie) — those need client-side gunzip + a TGS/Lottie player.","operationId":"filesGet","parameters":[{"in":"path","name":"kind","required":true,"schema":{"enum":["custom_emoji","photo","document","avatar","thumb"],"type":"string"}},{"in":"path","name":"id","required":true,"schema":{"description":"Telegram document_id / photo_id (int64 as decimal string)","type":"string"}},{"description":"HMAC signature part — required only when the server is configured with an API key.","in":"query","name":"sig","required":true,"schema":{"description":"HMAC signature — provided by parent API, never construct manually","type":"string"}},{"description":"Expiry unix-seconds — part of the signed URL.","in":"query","name":"exp","required":true,"schema":{"description":"Unix timestamp after which the signed URL is invalid","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"description":"Decompressed Lottie animation JSON (animated custom emoji)","type":"object"}},"application/octet-stream":{"schema":{"format":"binary","type":"string"}},"application/x-tgsticker":{"schema":{"format":"binary","type":"string"}},"audio/*":{"schema":{"format":"binary","type":"string"}},"image/*":{"schema":{"format":"binary","type":"string"}},"video/*":{"schema":{"format":"binary","type":"string"}}},"description":"File bytes with appropriate Content-Type"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unknown file kind, or non-numeric id"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Missing or invalid signature, or URL expired"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Resource not found or source hint missing (parent endpoint hasn't been called yet)"},"410":{"description":"file_reference expired — re-fetch the parent object to get a fresh URL"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Telegram fetch failed (upstream error)"},"503":{"description":"FLOOD_WAIT on the underlying Telegram fetch; retry later"}},"security":[],"summary":"Proxy Telegram media (photos, videos, documents, avatars, custom emojis) as plain HTTP"}},"/health":{"get":{"description":"Unauthenticated health check. Returns 200 with a small status body while the service is up.","operationId":"health","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"ok":{"type":"boolean"},"status":{"type":"string"}},"type":"object"}}},"description":"Service is up"}},"summary":"Liveness probe"}},"/peers.getInfo":{"post":{"description":"Resolves `target` and **automatically routes** to the appropriate MTProto call:\n- If target is a channel/supergroup/group → `channels.GetFullChannel` — returns channel shape.\n- If target is a user/bot → `users.GetFullUser` — returns user shape (with `about` text).\n\nUse this when you don't know the peer type ahead of time (e.g. bulk-resolving ad target usernames mixed channel/bot). Response always has a `type` field you can discriminate on.\n\nIf you know the target is a channel, prefer `/channels.getInfo` — cheaper, cached.\nIf you know the target is a bot, same-shape response is obtainable here; there's no dedicated `/bots.getInfo`.\n\n**Bot localizations:** pass `lang_codes` to fetch per-language `about` / `description` of a bot. Three accepted shapes:\n- Array of IETF codes: `[\"en\",\"ru\",\"pt-BR\"]`\n- Bare string `\"all\"` — expands to the full canonical set Telegram clients can actually render (Android ∪ iOS ∪ tdesktop, 73 codes + `\"\"` fallback)\n- Mixed inside array: `[\"en\", \"all\"]` (dedupes); `\"\"` queries the lang-agnostic fallback the bot owner set without a lang_code\n\n**How it works:** wraps `users.GetFullUser` in `invokeWithLayer(initConnection(lang_code=X))` per call, so each request sees the bot localized to that lang — no per-lang account or session required. Calls are fanned out across distinct pool accounts (one lang per account, parallel) using the cross-account peer cache.\n\n**Limitations:**\n- Localized `name` is **not** retrievable for foreign bots. Telegram only exposes that via the owner-only `bots.GetBotInfo`. The user-level path gives `about` + `description` + `commands_count` only.\n- When `\"\"` (fallback) is among the requested codes, langs whose `about`+`description` exactly match the fallback are **dropped** from `localizations` (Telegram serves the fallback for langs the owner didn't translate — dedupe avoids 70 false-positive \"translations\"). The fallback entry itself is always kept and marked `is_fallback: true`.\n- If `\"\"` is **not** requested, no dedupe happens — every lang is returned as-is (you'll see duplicates if the bot isn't translated for most langs).\n- Per-lang errors land as `{ error }` and don't fail the parent request. Only effective when target is a bot; silently ignored otherwise. Max 80 codes per call.","operationId":"peersGetInfo","requestBody":{"content":{"application/json":{"schema":{"properties":{"lang_codes":{"description":"Optional. Array of IETF lang codes (e.g. `[\"en\", \"ru\", \"pt-BR\"]`), the literal string `\"all\"` to query the full 73-code canonical set, or an array containing `\"all\"` to mix-and-merge. Empty string `\"\"` queries the bot's lang-agnostic fallback. For bots only; ignored on regular users.","oneOf":[{"items":{"maxLength":16,"pattern":"^[A-Za-z-]*$","type":"string"},"maxItems":80,"type":"array"},{"enum":["all"],"type":"string"}]},"target":{"description":"Any peer identifier: @username, t.me/ link, invite link, or numeric ID","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"oneOf":[{"properties":{"channel":{"$ref":"#/components/schemas/Channel"},"type":{"enum":["channel","supergroup","group"],"type":"string"}},"type":"object"},{"properties":{"about":{"description":"Bio / bot description from users.GetFullUser","type":["string","null"]},"localizations":{"additionalProperties":{"oneOf":[{"properties":{"about":{"description":"Localized about / bio text.","type":["string","null"]},"account_used":{"description":"Masked phone of the pool account that served this specific lang fetch.","type":"string"},"commands_count":{"description":"Number of bot commands localized in this lang (0 if none).","type":"integer"},"description":{"description":"Localized long description shown on the bot's start screen.","type":["string","null"]},"is_fallback":{"description":"True only for the `\"\"` entry — the lang-agnostic default the bot owner set without a lang_code. Other langs returning identical content to this one are dropped from the response.","type":"boolean"}},"type":"object"},{"properties":{"error":{"type":"string"}},"type":"object"}]},"description":"Present only when `lang_codes` was passed and target is a bot. Keys are the requested lang codes (with `\"\"` representing the lang-agnostic fallback). Values carry the per-language bot info pulled via `invokeWithLayer(initConnection(lang_code=X, query=users.GetFullUser))`, or `{ error }` if that specific lang's call failed.","type":"object"},"type":{"enum":["user","bot"],"type":"string"},"user":{"$ref":"#/components/schemas/User"}},"type":"object"}]}},"type":"object"}]}}},"description":"Peer info with `type` discriminator"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Auto-dispatching peer info — resolves target and routes to channel or user lookup"}},"/search.query":{"post":{"description":"Same as `/contacts.search`. `region` is required and echoed back (uppercased) in the response, but does **not** currently select a region-specific account — results are identical to `/contacts.search`. Found users are cached.","operationId":"searchQuery","requestBody":{"content":{"application/json":{"schema":{"properties":{"limit":{"default":10,"description":"default 10, clamped to 1–50.","maximum":50,"minimum":1,"type":"integer"},"q":{"description":"Search query","type":"string"},"region":{"description":"Optional geo hint (e.g. UZ, RU, US). **Best-effort soft hint**: the pool prefers an account registered in this region when one is available, but silently falls back across regions otherwise — it never fails the request for region reasons. Use GET /search.regions to see covered countries.","type":"string"}},"required":["q","region"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"channels":{"items":{"$ref":"#/components/schemas/SearchChannel"},"type":"array"},"query":{"type":"string"},"region":{"type":"string"},"users":{"items":{"$ref":"#/components/schemas/User"},"type":"array"}},"type":"object"}},"type":"object"}]}}},"description":"Regional search results"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Regional Telegram search — uses an account from the specified region"}},"/search.regions":{"get":{"operationId":"searchRegions","responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"items":{"properties":{"active":{"description":"Active accounts in this region","type":"integer"},"region":{"description":"Region code (e.g. UZ, RU, US)","type":"string"},"total":{"description":"Total accounts in this region","type":"integer"}},"type":"object"},"type":"array"}},"type":"object"}]}}},"description":"Array of regions"}},"summary":"List available account regions with active/total counts"}},"/status":{"get":{"operationId":"getStatus","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}},"description":"Current status"}},"summary":"Service health and account pool stats"}},"/users.getGifts":{"post":{"description":"Calls `payments.GetSavedStarGifts` to list gifts the recipient has chosen to display on their profile.\n\nEach item carries `pinned_to_top` (which gifts the recipient pinned), the wrapping `SavedStarGift` metadata (sender, date, attached message, collections), and the embedded `gift` payload — either a `regular` `StarGift` (with sticker media URL) or a `unique` `StarGiftUnique` with `attributes[]` (model/pattern documents + backdrop colors + original-details).\n\n**Target accepts** numeric user ID (must have been seen — like `/users.getInfo`), `@username`, t.me link, or numeric channel ID. For unseen numeric users, pass `chat` to bootstrap.\n\n**Pagination:** pass `offset` from the previous response's `next_offset` to fetch the next page. Empty/missing `next_offset` ⇒ end of list.","operationId":"usersGetGifts","requestBody":{"content":{"application/json":{"schema":{"properties":{"chat":{"description":"Optional. If a *numeric* `target` hasn't been seen, pass a chat where the user is active (`@username`, `t.me/username`, or numeric channel ID). The pool resolves it and scans its latest ~50 messages to seed the entity cache (no joining), then retries the lookup. Ignored for username targets or already-seen ids.","type":"string"},"exclude_unique":{"default":false,"description":"Skip unique (NFT-upgraded) gifts","type":"boolean"},"limit":{"default":50,"description":"default 50, clamped to 1–100.","maximum":100,"minimum":1,"type":"integer"},"offset":{"default":"","description":"Pagination cursor — pass next_offset from previous response","type":"string"},"sort_by_value":{"default":false,"description":"Sort by gift value (descending) instead of recipient's chosen order","type":"boolean"},"target":{"description":"User ID (numeric, must be seen), @username, t.me link, or channel ID","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"count":{"description":"Total gifts on the profile","type":"integer"},"gifts":{"items":{"$ref":"#/components/schemas/SavedStarGift"},"type":"array"},"next_offset":{"type":["string","null"]},"target":{"type":"string"}},"type":"object"}},"type":"object"}]}}},"description":"Page of saved gifts"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Enumerate saved star gifts on a user/channel profile (pinned + regular)"}},"/users.getInfo":{"post":{"description":"Returns a user's full profile via `users.GetFullUser`.\n\n**Target:** a numeric user ID (served only if the user has been seen — see the resolution model) or a `@username` (resolved live via `contacts.resolveUsername`). A target resolving to a non-user returns `NOT_A_USER`; an unseen numeric id returns `UPSTREAM_ERROR`.\n\n**`chat` parameter:** if a *numeric* target hasn't been seen, pass `chat` (a `@username`, `t.me/username`, or numeric channel ID where the user is active). The pool resolves that chat and scans its latest ~50 messages to seed the entity cache (no joining), then retries the lookup.","operationId":"usersGetInfo","requestBody":{"content":{"application/json":{"schema":{"properties":{"chat":{"description":"Optional. If a *numeric* `target` hasn't been seen, pass a chat where the user is active (`@username`, `t.me/username`, or numeric channel ID). The pool resolves it and scans its latest ~50 messages to seed the entity cache (no joining), then retries the lookup. Ignored for username targets or already-seen ids.","type":"string"},"target":{"description":"Numeric user ID only. Must have been previously seen by a pool account.","pattern":"^\\d+$","type":"string"}},"required":["target"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"$ref":"#/components/schemas/User"}},"type":"object"}]}}},"description":"User info from cache"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"User not in cache, invalid target format, or no active account that has seen this user"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Get full user profile (calls users.GetFullUser on the pool account that has seen the target)"}}},"security":[{"apiKey":[]},{"bearerAuth":[]}],"servers":[{"url":"http://localhost:3000"}]}