Operations — chats, users and the rest
This page continues Operations — messages. The rules it defines (order of checks, error codes, the chat argument, scope and hidden chats, rate buckets, the user action window, async) apply here unchanged; they are not repeated. Summary: Call protocol (ops). Python wrappers: Python — messages and chats.
Reminders that matter below:
- A request is
{"op": ..., "a": {...}}, optionally with"async": true; the answer is{"ok": true, "v": ...}or{"ok": false, "e": code, "m": message}. - Limits are charged before the permission check, so a refused call still spends its token.
- Optional integer arguments of the wrong type are treated as absent; optional booleans of the wrong type give
invalid; out-of-rangelimitvalues are clamped. - Secret chats and Saved Messages look like nonexistent chats (
not_found); the service chat777000and chats outside the granted scope givedenied(“this chat”). - No operation on this page ever returns
quota.
Chat object
Section titled “Chat object”| Key | Type | Notes |
|---|---|---|
id |
int | peer id |
title |
string | |
type |
string | user, bot, group, forum, supergroup, channel (self and secret chats are never shown) |
unread |
int | unread count |
pinned |
bool | pinned in the main chat list |
archived |
bool | in the archive |
chats.list
Section titled “chats.list”Permission: chats.list. No bucket. Wait: 1.5 s.
freshgram.chats.list(limit=100){"op": "chats.list", "a": {"limit": 100}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
limit |
int | no | 100 | clamped to 1..500 |
Result: array of chat objects, main list first, then the archive. At most 5000 chats are scanned; chats outside the granted scope, secret chats, Saved Messages and 777000 are left out, so fewer than limit can come back. Errors: denied, unavailable.
chat.info
Section titled “chat.info”Permission: chat.info. No bucket.
freshgram.chats.info(chat){"op": "chat.info", "a": {"chat": 123}}| Argument | Type | Required |
|---|---|---|
chat |
int | yes |
Result: one chat object. Errors: denied (permission; scope; 777000), invalid (“chat”), not_found (unknown, secret, Saved Messages), unavailable.
chats.mark_read
Section titled “chats.mark_read”Mark a chat as read. Permission: messages.send. Throttle: action bucket.
freshgram.chats.mark_read(chat){"op": "chats.mark_read", "a": {"chat": 123}}Result: null. Errors: denied, invalid, not_found, limit (action bucket), unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the chat was not marked as read”.
chats.action
Section titled “chats.action”Show “typing…” and similar actions. Permission: messages.send. Per-chat limit: one call per 3 s per chat (cancel exempt). No action bucket.
freshgram.chats.action(chat, kind="typing", topic=0)freshgram.chats.action(chat, kind="cancel"){"op": "chats.action", "a": {"chat": 123, "kind": "typing", "topic": 0}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
chat |
int | yes | chat argument rules | |
kind |
string | no | typing |
typing, upload_photo, upload_video, upload_file, record_voice, upload_voice, record_video, record_round, choose_sticker, cancel. A kind that is not text, or longer than 32 bytes, silently becomes typing; any other text gives invalid |
topic |
int | no | 0 | >= 0 (invalid “topic”) |
Result: null. cancel stops the typing action. The 3 s timestamp is stored before the kind is validated, so an invalid kind also uses up the 3 s window. Errors: denied, invalid, not_found, limit (“chat actions are sent too often”), unavailable. The broker does not check which kind names exist: an unknown kind is rejected by the app, which answers with its own code and the fixed message “the action was not sent” (so kind problems can show up as invalid with that message).
chats.members and chats.admins
Section titled “chats.members and chats.admins”Permissions: chat.info and users.info (both). Throttle: members bucket (1 per second, shared by both). Wait: 10 s.
freshgram.chats.members(chat, limit=50, offset=0, query=None)freshgram.chats.admins(chat, limit=50, offset=0){"op": "chats.members", "a": {"chat": 123, "limit": 50, "offset": 0, "query": "ann"}}{"op": "chats.admins", "a": {"chat": 123}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
chat |
int | yes | chat argument rules | |
limit |
int | no | 50 | clamped to 1..200 |
offset |
int | no | 0 | 0 to 1000000 (invalid “offset”) |
query |
string | members only |
none | 1 to 256 bytes; searches name and username; ignored by admins |
Result: array of {"id", "name", "username", "role", "bot"}. role is member, admin or creator; in channel searches banned and left can also appear. Entries that are not loaded users, or that are hidden (secret), are skipped, so the list can be shorter than limit. Channels and supergroups need the right to view members; basic groups are read from the full chat and filtered in the app; a private chat is not_found.
| Code | When |
|---|---|
denied |
missing chat.info or users.info; scope; the account cannot see the member list of that channel |
invalid |
query empty/too long/not text; offset out of range |
not_found |
chat unknown/secret/Saved; not a group or channel |
limit |
members bucket |
failed |
server error |
unavailable |
no answer |
chats.ban and chats.unban
Section titled “chats.ban and chats.unban”Permission: chats.moderate. Throttle: moderation bucket (1 per second, shared). Requires a user action within the last 10 s.
freshgram.chats.ban(chat, user)freshgram.chats.unban(chat, user){"op": "chats.ban", "a": {"chat": 123, "user": 777}}| Argument | Type | Required | Validation |
|---|---|---|---|
chat |
int | yes | chat argument rules |
user |
int | yes | > 0; not 777000; not yourself (denied “this user”) |
Result: null. The request to Telegram is sent without waiting; a server refusal is not reported. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the member was not changed”.
| Code | When |
|---|---|
denied |
no chats.moderate; scope; the user is you or 777000; no user action in the last 10 s (“only right after a user action”); you are not allowed to ban members in that chat |
invalid |
user missing or <= 0; unban in a basic group (only channels and supergroups can be unbanned) |
not_found |
chat or user not known to the app, hidden, or the chat is neither a group nor a channel |
limit |
moderation bucket |
chats.mute
Section titled “chats.mute”Permission: chats.manage. No bucket.
freshgram.chats.mute(chat, seconds=3600)freshgram.chats.mute(chat, forever=True)freshgram.chats.mute(chat) # unmute{"op": "chats.mute", "a": {"chat": 123, "seconds": 3600}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
chat |
int | yes | chat argument rules | |
seconds |
int | no | 0 | 0 to 31,622,400 (366 days); 0 with forever false unmutes |
forever |
bool | no | false | not together with a non-zero seconds |
Result: null. Errors: denied, invalid (“seconds or forever”), not_found, unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the chat was not changed”.
chats.archive and chats.pin
Section titled “chats.archive and chats.pin”Permission: chats.manage. No bucket.
freshgram.chats.archive(chat, archived=True)freshgram.chats.pin(chat, pinned=True){"op": "chats.archive", "a": {"chat": 123, "archived": true}}{"op": "chats.pin", "a": {"chat": 123, "pinned": true}}| Argument | Type | Required | Default |
|---|---|---|---|
chat |
int | yes | |
archived (archive) / pinned (pin) |
bool | no | true |
Result: null. chats.pin works on the main list; pinning an already pinned chat succeeds; when the pinned list is full it answers limit. chats.archive does not wait for the server. Errors: denied, invalid (“archived or pinned”), not_found, limit (pin list full), unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the chat was not changed”.
(messages.pin and messages.unpin, which pin a single message, are in Operations — messages.)
User object
Section titled “User object”| Key | Type | Notes |
|---|---|---|
id, name, username |
int, string, string | |
bot, premium, contact, self |
bool | |
status |
string | bot, online, recently, week, month, long_ago, offline, hidden |
last_seen |
int | unix time of the last online moment, 0 if unknown |
has_photo |
bool |
Users operations take the user id, not a chat; they are not filtered by the chat scope. Secret peers and the service user 777000 are never returned.
users.info
Section titled “users.info”Permission: users.info. No bucket.
freshgram.users.info(user){"op": "users.info", "a": {"id": 777}}| Argument | Type | Required | Validation |
|---|---|---|---|
id |
int | yes | > 0 (invalid); 777000 gives denied (“this user”) |
Result: user object. Only users the app already knows are found: not_found otherwise (also for a secret peer or an id that is a chat). Errors: denied, invalid, not_found, unavailable.
users.resolve
Section titled “users.resolve”Find a user by username. Permission: users.info. Throttle: resolve bucket (1 per second, shared with channels.resolve). Wait: 10 s.
freshgram.users.resolve("@durov"){"op": "users.resolve", "a": {"username": "durov"}}| Argument | Type | Required | Validation |
|---|---|---|---|
username |
string | yes | up to 64 bytes; after removing one leading @, 4 to 32 characters of A-Z a-z 0-9 _ (invalid “username”) |
Result: user object. Errors: denied (permission; the result is 777000), invalid, not_found (no such username, or it is not a user), limit (resolve bucket), unavailable.
users.photo
Section titled “users.photo”Current profile photo (full size). Permission: users.info. Throttle: action bucket. Wait: up to 40 s.
freshgram.users.photo(user){"op": "users.photo", "a": {"id": 777}}Result: {"mime": "image/jpeg" | "video/mp4", "data": bytes}. A video avatar comes as MP4. Errors: denied (permission; 777000), invalid (id <= 0), not_found (not a known user, secret, or no photo), limit (over 100 MiB, or the action bucket), failed (not loaded within 30 s), unavailable.
users.block and users.unblock
Section titled “users.block and users.unblock”Permission: contacts.manage. No bucket.
freshgram.users.block(user)freshgram.users.unblock(user){"op": "users.block", "a": {"id": 777}}Argument id (int, > 0, not 777000). Result: null. Errors: denied (permission; 777000), invalid, not_found (user unknown to the app, secret, or yourself), unavailable. The request is sent without waiting. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the user was not changed”.
Channels
Section titled “Channels”Channel object
Section titled “Channel object”{"id", "title", "username", "type", "about", "verified", "scam", "fake", "has_photo"} plus "members" (omitted if the count is unknown). type is channel, supergroup or forum. All channels.* operations accept supergroups and forums too.
channels.info
Section titled “channels.info”Permission: chat.info. No bucket. Wait: 10 s (waits up to 3 s for the full channel info, then returns what is known: about can be empty and members missing).
freshgram.channels.info(channel){"op": "channels.info", "a": {"id": 1001}}| Argument | Type | Required | Validation |
|---|---|---|---|
id |
int | yes | > 0; outside the scope or 777000 gives denied (“this channel”) |
Errors: denied, invalid, not_found (not a channel or supergroup, unknown, or secret), unavailable.
channels.resolve
Section titled “channels.resolve”Permission: chat.info. Throttle: resolve bucket (shared with users.resolve).
freshgram.channels.resolve("@telegram"){"op": "channels.resolve", "a": {"username": "telegram"}}username as for users.resolve. Result: channel object. Errors: denied (permission; the channel is out of scope or 777000), invalid, not_found, limit, unavailable.
channels.photo
Section titled “channels.photo”Permission: chat.info. Throttle: action bucket. Same result as users.photo ({"mime", "data"}), and the same errors. The channel must be in scope (denied “this channel”).
freshgram.channels.photo(channel){"op": "channels.photo", "a": {"id": 1001}}Contacts and folders
Section titled “Contacts and folders”contacts.list
Section titled “contacts.list”Permission: contacts.list. No bucket.
freshgram.contacts.list(limit=200){"op": "contacts.list", "a": {"limit": 200}}| Argument | Type | Default | Validation |
|---|---|---|---|
limit |
int | 200 | clamped to 1..1000 |
Result: array of {"id", "name", "username"}; yourself, 777000 and users outside the chat scope are left out (after the limit is applied, so the list can be shorter). Errors: denied, unavailable.
contacts.add
Section titled “contacts.add”Add a user the app already knows to your contacts. Permission: contacts.manage. No bucket. Wait: 100 s.
freshgram.contacts.add(user, "Bob", last_name=""){"op": "contacts.add", "a": {"id": 777, "first_name": "Bob", "last_name": ""}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
id |
int | yes | > 0; not 777000 (denied) |
|
first_name |
string | yes | 1 to 64 bytes | |
last_name |
string | no | “” | up to 64 bytes |
Result: null. Errors: denied, invalid, not_found (user unknown, hidden, or a bot), failed (server error), unavailable.
folders.list
Section titled “folders.list”Permission: folders.list. No arguments, no bucket.
freshgram.folders.list(){"op": "folders.list", "a": {}}Result: array (at most 100) of {"id": int, "title": string, "chats": [int]}. Each folder lists up to 500 loaded chats, without secret chats, Saved Messages, 777000 and chats outside the scope. The default “All chats” folder is not included. Errors: denied, unavailable.
Stickers
Section titled “Stickers”All three need messages.send (searching is treated as part of sending).
stickers.send
Section titled “stickers.send”Send an installed or recent sticker by id. Throttle: send bucket.
sent = freshgram.stickers.send(chat, sticker_id, reply_to=0, topic=0, silent=False){"op": "stickers.send", "a": {"chat": 123, "id": 5368324170671202286, "silent": false}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
chat |
int | yes | invalid, then denied, then not_found |
|
id |
int | yes | > 0; must be in the recent stickers or an installed set |
|
reply_to, topic |
int | no | 0 | >= 0 |
silent |
bool | no | false |
Result: {"local_id": N} (never an id: the call does not wait for the server, even with messages.ids). Errors: denied (permission; scope; stickers restricted in that thread; the chat charges Stars per message), invalid, not_found (chat; the sticker is not known; reply not cached), limit (send bucket; slow mode), unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the sticker was not sent”.
stickers.search
Section titled “stickers.search”Installed stickers for an emoji. No bucket.
freshgram.stickers.search("😀", limit=20){"op": "stickers.search", "a": {"emoji": "😀", "limit": 20}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
emoji |
string | yes | 1 to 64 bytes (invalid “emoji”); a value that is not an emoji gives an empty list |
|
limit |
int | no | 20 | clamped to 1..100 |
Result: array of {"id", "emoji", "set", "animated", "video"}.
stickers.recent
Section titled “stickers.recent”Recently used stickers. Argument limit (default 20, clamped 1..100); same result as stickers.search.
freshgram.stickers.recent(20){"op": "stickers.recent", "a": {"limit": 20}}bots.inline_query
Section titled “bots.inline_query”Ask an inline bot; nothing is sent to any chat. Permission: bots.inline. Throttle: inline bucket (1 per second, shared with bots.press_button). Wait: 10 s.
results = freshgram.bots.inline_query("@gif", "cats", chat=None){"op": "bots.inline_query", "a": {"bot": "gif", "query": "cats", "chat": 123}}| Argument | Type | Required | Validation |
|---|---|---|---|
bot |
string or int | yes | a username (optional @, 4 to 32 characters of A-Z a-z 0-9 _) or a positive user id; 777000 gives denied (“this bot”) |
query |
string | yes | up to 256 bytes (may be empty) |
chat |
int | no | context chat, with the usual chat argument rules (scope, hidden chats) |
Result: array (at most 50) of {"query_id": int, "id": string, "title", "description", "text"}.
| Code | When |
|---|---|
denied |
no bots.inline; the id form is 777000; chat out of scope |
invalid |
bot missing or malformed, query missing or too long (“bot or query”); chat not an integer |
not_found |
the username does not resolve; the user is not a bot, is hidden or is blocked; the id is not a known user; chat unknown/secret |
limit |
inline bucket |
failed |
the bot has no inline mode, or the request failed |
unavailable |
no answer in 10 s |
bots.send_inline_result
Section titled “bots.send_inline_result”Send a result of bots.inline_query to a chat. Permissions: bots.inline and messages.send. No send bucket and no inline bucket (only the global call limit applies). Fire-and-forget.
freshgram.bots.send_inline_result(chat, results[0]["query_id"], results[0]["id"], reply_to=0, topic=0, silent=False){"op": "bots.send_inline_result", "a": {"chat": 123, "query_id": 99, "id": "r1"}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
chat |
int | yes | chat argument rules | |
query_id |
int | yes | integer | |
id |
string | yes | 1 to 128 bytes | |
reply_to, topic |
int | no | 0 | >= 0 |
silent |
bool | no | false |
Result: {"local_id": null} (the Python SDK returns nothing). Errors: denied (either permission; scope; you cannot write there), invalid, not_found (chat; reply not cached), limit (slow mode), unavailable. A refusal by Telegram (expired query_id, …) is not reported. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the result was not sent”.
bots.press_button
Section titled “bots.press_button”Press a button of a bot’s inline keyboard. Permission: bots.inline. Throttle: inline bucket. Wait: up to 100 s.
answer = freshgram.bots.press_button(chat, message, row=0, column=1){"op": "bots.press_button", "a": {"chat": 123, "id": 4567, "row": 0, "column": 1}}| Argument | Type | Required | Validation |
|---|---|---|---|
chat |
int | yes | chat argument rules |
id |
int | yes | > 0 server id |
row, column |
int | yes | >= 0 |
Result: {"text": string, "alert": bool, "url": string}. A callback button sends the callback and returns the bot’s answer; a URL button returns its url without any request. Buttons are listed in buttons of the message object.
| Code | When |
|---|---|
denied |
no bots.inline; scope |
invalid |
the ids/coordinates malformed; the button is neither a callback nor a URL button (for example “switch inline”, payments); chat not an integer |
not_found |
chat unknown/secret/Saved; message not cached; no inline keyboard; row or column out of range |
limit |
inline bucket |
failed |
the bot or the server did not accept the callback (including buttons that need a password) |
Composer
Section titled “Composer”All three need composer.draft. They differ from other chat operations in one detail: a chat that is missing, not an integer, out of scope or 777000 gives denied (“this chat”), never invalid. A chat that is unknown, secret or Saved Messages gives not_found. No bucket; wait 1.5 s.
composer.draft
Section titled “composer.draft”freshgram.composer.draft(chat){"op": "composer.draft", "a": {"chat": 123}}Result: the plain text of the chat’s current draft ("" if none). Entities and the reply are not included.
composer.set_draft
Section titled “composer.set_draft”freshgram.composer.set_draft(chat, "text"){"op": "composer.set_draft", "a": {"chat": 123, "text": "text", "replace": false}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
chat |
int | yes | see above | |
text |
string | yes | 0 to 4096 bytes; plain text only | |
replace |
bool | no | false | must be a bool; a non-empty existing draft is replaced only with replace: true |
Result: null. The cursor goes to the end; an existing reply of the draft is kept. Errors: denied, invalid (“text or replace”; “the draft is not empty, pass replace”), not_found, failed (“the draft was not set”), unavailable.
composer.insert
Section titled “composer.insert”Insert text into the draft at the saved cursor, or at the end.
freshgram.composer.insert(chat, "text"){"op": "composer.insert", "a": {"chat": 123, "text": "text"}}Argument text: 1 to 4096 bytes (invalid “text”). Result: null; the cursor moves after the inserted text. Errors: denied, invalid, not_found, unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the text was not inserted”.
Account and settings
Section titled “Account and settings”These have no arguments and no bucket.
| Operation | Permission | Python | Result |
|---|---|---|---|
account.info |
account.info |
freshgram.account.info() |
{"id": int, "name": string, "username": string, "premium": bool} (the phone number is not exposed) |
stars.balance |
stars.balance |
freshgram.stars.balance() returns the number |
{"balance": int} (whole Stars; waits up to 10 s for the balance to load) |
settings.read |
settings.read |
freshgram.settings.read() |
{"language": string, "theme": "dark" | "light", "streamerMode": bool} (note the camelCase key) |
{"op": "account.info", "a": {}}{"op": "stars.balance", "a": {}}{"op": "settings.read", "a": {}}Errors for all: denied (permission), unavailable. stars.balance answers {"balance": 0} when the account is not available.
Ghost mode
Section titled “Ghost mode”Permission: ghost.control (classified as a high-risk permission).
ghost.get
Section titled “ghost.get”freshgram.ghost.get(){"op": "ghost.get", "a": {}}Result: {"active": bool, "send_read_messages": bool, "send_read_stories": bool, "send_online": bool, "send_typing": bool}. Errors: denied, not_found (no such account), unavailable.
ghost.set
Section titled “ghost.set”freshgram.ghost.set(True)freshgram.ghost.set(False) # only right after a user action{"op": "ghost.set", "a": {"enabled": true}}| Argument | Type | Required | Validation |
|---|---|---|---|
enabled |
bool | yes | a bool, else invalid. Turning it off needs a user action within the last 10 s (denied “only right after a user action”); turning it on does not |
Result: null. Errors: denied, invalid (“enabled”), not_found, unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “ghost mode was not changed”.
Notifications
Section titled “Notifications”notify.show
Section titled “notify.show”Show a system notification. Permission: notifications (there is no permission called notify.show). Throttle: one per 5 seconds. No user action needed.
freshgram.notify.show("Backup done", title="My plugin"){"op": "notify.show", "a": {"title": "My plugin", "text": "Backup done"}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
text |
string | yes | 1 to 256 bytes | |
title |
string | no | “” | up to 64 bytes; empty means the app name |
Result: null. With native desktop notifications enabled it is a system notification; otherwise it is shown as an in-app toast “title: text”. Errors: denied, invalid (“title or text”), limit (“notifications are shown too often”), not_found, unavailable. If the app itself refuses, the call answers with the code the app reports (usually failed) and the fixed message “the notification was not shown”.
Providing translation and AI
Section titled “Providing translation and AI”These are the replies of a plugin that declared contributes.translator / contributes.aiProvider. The request arrives as an event; the plugin answers with the matching operation using the event’s ticket. They are not tied to a chat and have no bucket.
translate.reply
Section titled “translate.reply”Answer a translate.request event. Permission: translate.provide.
The request event: {"type": "translate.request", "ticket": N, "to": "<two-letter code>", "format": "plain", "items": [string, ...]} (1 to 20 items, together at most 24 KiB; at most 8 requests of a plugin wait at once; the ticket expires after 20 s).
@freshgram.on_translatedef translate(items, to): return [text.upper() for text in items] # the SDK calls translate.reply for you{"op": "translate.reply", "a": {"ticket": 5, "items": ["HELLO"], "format": "plain"}}{"op": "translate.reply", "a": {"ticket": 5, "error": "language not supported"}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
ticket |
int | yes | ||
items |
array of strings | unless error |
each up to 32 KiB; up to 20 items; at most 96 KiB together | |
format |
string | no | plain |
plain, markdown, html; applied to every item (markup becomes entities) |
error |
string | no | “” | up to 200 bytes; a non-empty error fails the translation and items is ignored |
Either a non-empty error or items must be given. When error is empty the number of items must equal the number of requested items.
| Code | When |
|---|---|
denied |
no translate.provide |
invalid |
missing ticket, items and error; an item not text or over 32 KiB; bad format; markup error or an item empty once its markup is stripped; the item count differs from the request (the ticket stays valid, you may answer again) |
limit |
more than 20 items or over 96 KiB in total (“the answer is too large”) |
not_found |
no such ticket: unknown, expired, already answered, or issued to another plugin (“no such translation”) |
unavailable |
translation is not available in this host |
An item answered as the empty string is counted as a failed item by the app.
ai.reply
Section titled “ai.reply”Answer an ai.request event. Permission: ai.provide (high risk: it receives the text the user wants rewritten).
The request event: {"type": "ai.request", "ticket": N, "text": string, "format": "plain", "proofread": bool, "emojify": bool, "translate_to"?, "tone"?, "prompt"?}. At most 4 requests of a plugin wait at once; the ticket expires after 60 s.
@freshgram.on_aidef rewrite(request): return request["text"].strip() # the SDK calls ai.reply for you{"op": "ai.reply", "a": {"ticket": 7, "text": "Result", "format": "plain"}}{"op": "ai.reply", "a": {"ticket": 7, "error": "busy"}}| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
ticket |
int | yes | ||
text |
string | unless error |
“” | up to 64 KiB |
format |
string | no | plain |
plain, markdown, html |
error |
string | no | “” | up to 200 bytes; non-empty error fails the request and text is ignored |
Errors: denied (no ai.provide), invalid (no ticket; error and text both empty; too long; bad format; markup error), not_found (“no such request”: unknown, expired, answered, or another plugin’s ticket), unavailable (no AI provider in this host).
Plugin-to-plugin messages
Section titled “Plugin-to-plugin messages”Both need the permission plugins.message (on both plugins). No bucket.
plugins.send
Section titled “plugins.send”Send data to another plugin.
freshgram.plugins.send("other.plugin", {"hello": 1}){"op": "plugins.send", "a": {"to": "other.plugin", "data": {"hello": 1}}}| Argument | Type | Required | Validation |
|---|---|---|---|
to |
string | yes | 1 to 64 bytes; not this plugin’s own id (invalid “to”) |
data |
any CBOR/JSON value | no (default null) | nesting depth at most 12, encoded size at most 64 KiB (limit “the message is too large”); an unencodable or too deep value gives invalid (“data”) |
Result: null; delivery is asynchronous. The target receives {"type": "plugin.message", "from": "<sender id>", "data": ...}. Up to 1024 undelivered messages are queued in the host.
| Code | When |
|---|---|
denied |
no plugins.message; the sender is not a registered plugin; the target does not accept messages from this plugin (contributes.messages.from). The message of the error is the target id |
not_found |
no plugin with that id |
invalid |
bad to or data |
limit |
message over 64 KiB, or the host’s mail queue is full |
unavailable |
plugin messages not available in this host |
plugins.peers
Section titled “plugins.peers”freshgram.plugins.peers(){"op": "plugins.peers", "a": {}}Result: array of plugin ids this plugin may send to (those that accept messages from it or from "*"). Errors: denied, unavailable.
