PPico Help فارسیOpen Pico

Pico Developers 🤖

Build bots for Pico Messenger

What is a Pico bot?

A bot is a small program that talks to people on Pico like a normal contact. You write it in any programming language, run it wherever you like, and it talks to Pico over a simple HTTPS API.

The Bot API follows the same shape as Telegram's, so if you have written a Telegram bot before, you already know most of it. 🎉

Quick start

  1. Create the bot. In Pico open @botfather and send /newbot. Choose a name and a username that ends in bot.
  2. Copy the token. BotFather sends a token like 123456:AbCdEf-your-token. Tap it to copy. You can see it again whenever you want: /mybots → your bot → 🔑 API token.
  3. Say hello to your bot. Open it and press Start. Bots can never message someone who has not written to them first.
  4. Run a program. Use one of the examples below, put your token in, and write to your bot. 🎈
import requests

TOKEN = "123456:AbCdEf-your-token"
API = f"https://picogram.ir/bot{TOKEN}"
offset = 0

while True:
    r = requests.get(f"{API}/getUpdates", params={"offset": offset, "timeout": 30}, timeout=40).json()
    for u in r["result"]:
        offset = u["update_id"] + 1
        m = u.get("message")
        if m and m.get("text"):
            requests.post(f"{API}/sendMessage", json={"chat_id": m["chat"]["id"], "text": f"You said: {m['text']}"})
Python

How to connect

Every request goes to:

https://picogram.ir/bot<TOKEN>/<METHOD>
URL
  • 🔒 HTTPS only.
  • 📨 Send parameters as a query string, as a JSON body (content-type: application/json) or both. GET and POST both work.
  • 📦 The answer is always JSON: {"ok": true, "result": …} on success, or {"ok": false, "error_code": 400, "description": "…"} on failure.
  • 🐢 Rate limit: 600 requests per minute per IP address.

Telegram libraries

Most Telegram bot libraries let you change the API address. Point them at Pico and they work for every method listed on this page. Methods Pico does not have yet answer with an error.

# python-telegram-bot
Application.builder().token(TOKEN).base_url("https://picogram.ir/bot").build()

# aiogram 3
AiohttpSession(api=TelegramAPIServer.from_base("https://picogram.ir"))

# grammY
new Bot(TOKEN, { client: { apiRoot: "https://picogram.ir" } })

# Telegraf
new Telegraf(TOKEN, { telegram: { apiRoot: "https://picogram.ir" } })

# node-telegram-bot-api
new TelegramBot(TOKEN, { polling: true, baseApiUrl: "https://picogram.ir" })
Libraries

Languages and formats

Pico does not care what you write your bot in. If your language can make an HTTPS request and read JSON, it works: Python, JavaScript / TypeScript (Node, Deno, Bun), PHP, Go, Java, Kotlin, C#, Rust, Ruby, Dart, Swift, shell scripts… 🌍

Accepted
📡 TransportHTTPS (TLS 1.2+)
📝 Requestquery string, JSON body (application/json), or both
📤 ResponseJSON, UTF-8
🔤 TextUTF-8, 1 to 4096 characters per message
🔁 Getting messageslong polling with getUpdates or a webhook

Methods

MethodParametersWhat it does
getMe—Your bot's id, name and username.
getUpdatesoffset, timeout (0–50 s), limit (1–100)Long polling. Pass offset = last update_id + 1 to confirm what you already handled.
sendMessagechat_id, text, parse_mode, reply_to_message_id, reply_markupSends a text message.
editMessageTextchat_id, message_id, text, parse_mode, reply_markupEdits a message your bot sent.
deleteMessagechat_id, message_idDeletes a message your bot sent.
answerCallbackQuerycallback_query_idTells Pico you handled a button tap.
sendChatActionchat_id, actionAccepted so existing code keeps working.
setMyCommands / getMyCommandscommands: JSON list of {command, description} (up to 100)The /commands menu of your bot. Commands are lowercase letters, digits and underscores.
getChatchat_idBasic info about a chat.
setWebhook / deleteWebhook / getWebhookInfourl, secret_tokenReceive updates by webhook instead of polling.

Photos, videos and files

Your bot can send everything people can send in Pico, and receive it too.

MethodFile parameterShown as
sendPhotophotoa photo
sendVideovideoa video that plays inside the chat
sendAnimationanimationa looping video or a GIF
sendAudioaudioan audio file
sendVoicevoicea voice message with a waveform
sendDocumentdocumentany other file
sendLocation, sendContact, sendDice—a tappable map link, a contact card, a dice roll 🎲

Every one of them also takes chat_id, caption (up to 1024 characters), parse_mode, reply_to_message_id and reply_markup. For videos and voice you can add duration, width and height.

The file itself can be given in three ways:

# 1) upload a file from your computer (multipart/form-data)
curl -F chat_id=42 -F caption="Look 👀" -F photo=@cat.jpg "https://picogram.ir/bot123456:AbCdEf-your-token/sendPhoto"

# 2) send a file Pico already has (a file_id from an update or an earlier message)
curl "https://picogram.ir/bot123456:AbCdEf-your-token/sendDocument" -d chat_id=42 -d document=<FILE_ID>

# 3) let Pico download a public https link for you
curl "https://picogram.ir/bot123456:AbCdEf-your-token/sendVideo" -d chat_id=42 --data-urlencode "video=https://example.com/clip.mp4"

# receive: getFile gives a path, then download it
curl "https://picogram.ir/bot123456:AbCdEf-your-token/getFile?file_id=<FILE_ID>"
curl -O "https://picogram.ir/file/bot123456:AbCdEf-your-token/files/<FILE_ID>"
curl
  • 📤 Upload with multipart/form-data (one file per request).
  • 🆔 A file_id that Pico gave you, to send the same file again without uploading it.
  • 🔗 A public https link: Pico downloads it for you (up to 50 MB, public addresses only).

Receiving files

When someone sends your bot a photo, video, voice message or file, the update has the matching field (photo, video, voice, document), a file_id, and the caption.

{
  "update_id": 120,
  "message": {
    "message_id": 940,
    "from": { "id": 7, "is_bot": false, "first_name": "Sara" },
    "chat": { "id": 42, "type": "private" },
    "caption": "my cat",
    "photo": [ { "file_id": "k2s9…", "width": 1280, "height": 960, "file_size": 183412 } ]
  }
}
JSON

Call getFile to get a file_path, then download https://picogram.ir/file/bot<TOKEN>/<file_path>. The largest file is 200 MB (50 MB for uploads and links from a bot).

Forward, copy and edit

forwardMessage and copyMessage (chat_id, from_chat_id, message_id), editMessageCaption, editMessageReplyMarkup, pinChatMessage and unpinChatMessage.

Groups, channels and admin methods

Add your bot to a group or channel, make it an admin with the rights it needs, and it can manage the chat.

MethodNeeds the rightWhat it does
getChatMember—Status of a person: creator, administrator, member, left or kicked, with their rights.
getChatAdministrators—The owner and all admins.
getChatMemberCount—How many members.
banChatMember / unbanChatMemberbanRemove and block someone from coming back, or let them return.
setChatTitle / setChatDescriptioninfoChange the name or description.
pinChatMessage / unpinChatMessagepinPin or unpin a message.
leaveChat—The bot leaves.
promoteChatMemberadminsMake someone an admin with can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_restrict_members, can_invite_users, can_pin_messages, can_promote_members (never more than the bot has itself).
setChatPermissionsbanWhat ordinary group members may do.
exportChatInviteLink / createChatInviteLink / revokeChatInviteLinkinviteGet or reset the invite link.
setChatPhoto / deleteChatPhotoinfoChange the picture.
unpinAllChatMessagespinRemove every pin.
deleteMessages, forwardMessages, copyMessages, sendMediaGroup—Several messages at once; an album of up to 10 photos, videos, audio files or documents.

Admin rights

Owners and admins give people (and bots) exactly the rights they need: change info, post messages (channels), edit and delete messages, ban people, invite people, pin messages and add new admins. The owner can also hand the chat over to someone else, and set what ordinary members of a group may do (send messages, send media, add people, pin, change info).

Your bot's profile

setMyName, getMyName, setMyDescription, getMyDescription. Change its picture in @botfather: /mybots → your bot → 📷 Edit picture.

Button answers

answerCallbackQuery with text shows a short message to the person who tapped the button.

Membership updates

When your bot is added to or removed from a group or channel it gets a my_chat_member update with the old and new status (member, left or kicked).

Updates: what your bot receives

Every item from getUpdates (or every webhook call) has an update_id and one of two things:

💬 A message

{
  "update_id": 118,
  "message": {
    "message_id": 931,
    "from": { "id": 7, "is_bot": false, "first_name": "Sara", "username": "sara" },
    "date": 1767225600,
    "text": "hello",
    "chat": { "id": 42, "type": "private" }
  }
}
JSON

text is the plain text. If the person used formatting, formatted_text also contains the original with Pico's formatting symbols.

👆 A button tap

{
  "update_id": 119,
  "callback_query": {
    "id": "931.1767225700000",
    "from": { "id": 7, "is_bot": false, "first_name": "Sara", "username": "sara" },
    "data": "yes",
    "message": { "message_id": 931, "chat": { "id": 42, "type": "private" }, "text": "Pick one" }
  }
}
JSON

In groups and channels a bot only sees commands (/start), messages that mention it (@yourbot) and replies to its own messages.

Formatting text

Add parse_mode to sendMessage and editMessageText: HTML, MarkdownV2 or Markdown, exactly like Telegram. Without it the text is sent as typed.

HTML

<b>bold</b> <i>italic</i> <u>underline</u> <s>strike</s> <tg-spoiler>spoiler</tg-spoiler>
<a href="https://picogram.ir">link</a> <code>inline code</code>
<pre><code class="language-python">print("hi")</code></pre>
<blockquote>quote</blockquote>
HTML

MarkdownV2

*bold* _italic_ __underline__ ~strike~ ||spoiler||
[link](https://picogram.ir) `inline code`
```python
print("hi")
```
>quote  (escape these with a backslash: _ * [ ] ( ) ~ ` > # + - = | { } . !)
MarkdownV2

Pico supports bold, italic, underline, strikethrough, spoiler, inline code, code blocks with a language, links, and block quotes (also collapsible ones with <blockquote expandable>). Mentions (@name), hashtags, links, e-mail addresses and /commands are recognised automatically. If the formatting is broken you get 400 can't parse entities.

Buttons (inline keyboards)

Add reply_markup to a message to show buttons under it. A button has either callback_data (your bot gets a callback_query when it is tapped) or a url (opens a link).

{
  "chat_id": 42,
  "text": "Pick one 👇",
  "reply_markup": {
    "inline_keyboard": [
      [ { "text": "✅ Yes", "callback_data": "yes" }, { "text": "❌ No", "callback_data": "no" } ],
      [ { "text": "🌐 Website", "url": "https://example.com" } ]
    ]
  }
}
JSON
LimitValue
Rowsup to 8
Buttons per rowup to 8
Button text1–64 characters
callback_dataup to 64 characters
urlhttp(s) links only

When someone taps a button, call answerCallbackQuery and then editMessageText to update the message, or send a new one.

Reply keyboards

Besides buttons under a message, reply_markup can show keys above the message box. Tapping a key sends its text as a normal message.

{ "keyboard": [[{"text":"One"},{"text":"Two"}],[{"text":"Cancel"}]], "resize_keyboard": true, "one_time_keyboard": true, "input_field_placeholder": "Choose…" }
JSON
  • one_time_keyboard: the keys hide after one tap (people can bring them back with the keyboard button).
  • {"remove_keyboard": true}: removes the keys.
  • {"force_reply": true}: the message box replies to your message straight away.

Webhooks

Instead of polling, Pico can call your server whenever something happens.

curl "https://picogram.ir/bot123456:AbCdEf-your-token/setWebhook" \
  --data-urlencode "url=https://bot.example.com/pico-hook" \
  --data-urlencode "secret_token=my_secret_123"
curl
  • 🔒 The address must be https on port 443 or 8443, and must resolve to a public address.
  • 🔑 If you set secret_token, every call carries the header X-Pico-Bot-Api-Secret-Token. Check it.
  • 📮 Pico sends the update as JSON in a POST. Answer with 2xx within 10 seconds.
  • 🔁 A failed delivery is tried 3 times (now, after 1.5 s, after 8 s). getWebhookInfo shows the last error.
  • ⚠️ While a webhook is set, getUpdates answers 409 Conflict. Call deleteWebhook to go back to polling.

Rules and limits

TopicRule
UsernameEnds in bot, 4–32 letters, digits or underscores, starts with a letter. Names containing pico are reserved for the Pico team.
Starting a chatA bot can only write to people who wrote to it first.
Groups and channelsBots see commands, mentions and replies to themselves.
EncryptionChats with bots, groups and channels are not private chats: they are protected in transit and in storage. Private chats between two people are encrypted with keys tied to their accounts.
Message size1–4096 characters.
Request rate600 requests per minute per IP.
Kept updates24 hours.

🚧 Not available yet

Inline mode (answerInlineQuery), payments, games, polls, stickers, sendVideoNote, commands per language or scope, and Mini Apps. Tell us what you need through @support in Pico. 💙

Errors

CodeMeaningWhat to do
401UnauthorizedThe token is wrong or was revoked. Check it in BotFather.
400Bad RequestA parameter is missing or invalid, or the formatting cannot be parsed. Read description.
403ForbiddenThe bot is not in that chat, or the person has not written to the bot first.
404Not FoundThe method does not exist (yet).
409ConflictA webhook is set. Use deleteWebhook or use the webhook.
429/5xxSlow down / server problemWait a moment and try again.