Developers

The fdyappa Bot API

Build a bot that lives inside fdyappa chats. The API is Telegram-shaped — if you've built a Telegram bot, you already know this one. Long-poll from a laptop with no public URL, or set a webhook.

Introduction

The whole design rests on one idea: a bot is a user. It gets a real account, so DMs, groups, search, avatars, receipts and push all work for it with no special cases. What makes it a bot is a token and an update queue.

Every call is a plain HTTPS request to https://fdyappa.in/bot<token>/<method>. Parameters go as a JSON body (or query string). Every response is { "ok": true, "result": … } on success, or { "ok": false, "error_code": N, "description": "…" } on failure.

Quick start

# Long-poll for updates (held open up to 30s), then reply
curl "https://fdyappa.in/bot$TOKEN/getUpdates?timeout=30"

curl -X POST "https://fdyappa.in/bot$TOKEN/sendMessage" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 123, "text": "Hello from my bot 👋"}'
No public URL needed. Long polling means your bot makes outbound requests only — it runs behind any NAT or firewall, on a laptop or a Raspberry Pi. "run this script and you have a bot" is literally true.

Tokens & base URL

A token looks like 42:AbC…xyz — the number before the colon is the bot's user id, the rest is a secret. Put the whole token straight into the path:

https://fdyappa.in/bot42:AbC…xyz/getMe

The token is stored only as a hash on the server, so it can't be recovered — if you lose it, revoke & reissue from the app (the old token stops working immediately).

What's a chat_id?

Every method that sends something takes a chat_id — the conversation you're writing to. There are two ways to give it, and one important thing to know:

The chat_id is NOT the user's id. Unlike some other bot platforms, a private chat here has its own conversation id, separate from the user's account id. In an update you'll see chat.id (the conversation, e.g. 57) and from.id (the sender's user id, e.g. 5) — they're different numbers. Send to chat.id; use from.id only for identity. Not sure of the id? Call getChat with a @username and it returns the numeric id to use.

Addressing by username never lets a bot skip consent: if you've never chatted with @alice (she hasn't opened your bot and pressed Start), the call returns a clear 404 rather than starting an unsolicited chat.

# these two are equivalent when 57 is your DM with @alice
curl -X POST "https://fdyappa.in/bot$TOKEN/sendMessage" -d '{"chat_id": 57,       "text": "hi"}'
curl -X POST "https://fdyappa.in/bot$TOKEN/sendMessage" -d '{"chat_id": "@alice", "text": "hi"}'

The rules

These aren't optional — they're what keep bots from becoming a spam cannon:


Receiving updates — getUpdates

GET

/getUpdates

Returns an array of updates. Held open (long-poll) until something happens or timeout seconds pass, so an idle bot costs one parked request instead of one per second.

ParamTypeNotes
offsetintThe update_id to start from. Passing N acknowledges everything below N (they're deleted). Use last_update_id + 1.
timeoutintSeconds to hold the request open (max 40). 0 returns immediately.
limitintMax updates to return (1–100, default 100).

The update object

Each update carries either a message or a callback_query:

// an incoming message
{
  "update_id": 1024,
  "message": {
    "message_id": 5567,
    "date": 1752400000,
    "chat": { "id": 123, "type": "private" },
    "from": { "id": 5, "username": "alice", "first_name": "Alice", "is_bot": false },
    "text": "/start",
    "reply_to_message_id": 5560
  }
}

// a button tap
{
  "update_id": 1025,
  "callback_query": {
    "id": "a1b2c3",
    "from": { "id": 5, "username": "alice" },
    "message": { "message_id": 5567, "chat": { "id": 123 } },
    "data": "like:42"
  }
}
Media you receive arrives in text with a prefix: [IMAGE]<url>, [VIDEO:secs]<url>, [VOICE:secs]<url>, or [FILE:name:bytes]<url>. Plain text has no prefix.

Webhooks (optional)

If your bot has a public HTTPS endpoint, skip polling: call setWebhook with a url and we POST each update to it as it happens. deleteWebhook switches back to long polling.

curl -X POST "https://fdyappa.in/bot$TOKEN/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://my-server.example/hook"}'

Sending — sendMessage

POST

/sendMessage

ParamTypeNotes
chat_idint / stringRequired. The conversation id (from an update's chat.id), or a @username for a 1:1 chat. See What's a chat_id?
textstringRequired. Up to 4096 chars.
reply_to_message_idintOptional. Send as a threaded reply.
reply_markupobjectOptional. An inline keyboard, a form, and/or a card.

Returns { ok, result: { message_id, date, chat, text } } — keep message_id if you plan to edit or delete it later.

sendPhoto & sendVideo

POST

/sendPhoto · /sendVideo

Same as sendMessage but pass a media URL instead of text: photo for /sendPhoto, video for /sendVideo (must be a public http(s) URL). chat_id is required; reply_markup is supported.

curl -X POST "https://fdyappa.in/bot$TOKEN/sendPhoto" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 123, "photo": "https://picsum.photos/800/600"}'

Looking up a chat — getChat

POST

/getChat

Resolve a chat_id — numeric or @username — into the chat's details, including the numeric id you send to. Handy when you know someone's handle but not their conversation id.

ParamTypeNotes
chat_idint / stringRequired. A conversation id or a @username.

For a 1:1 chat you get back the conversation id, the peer's user_id, username and first_name. For a group you get id, title and members_count.

curl -X POST "https://fdyappa.in/bot$TOKEN/getChat" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": "@alice"}'

// → { "ok": true, "result": {
//     "id": 57, "type": "private",
//     "user_id": 5, "username": "alice", "first_name": "Alice" } }
// send to result.id (57), not to user_id (5).

Inline keyboards

Attach buttons under any message with reply_markup. Each button has text plus either a callback_data (you get a callback_query update when tapped) or a url (opens in the browser). Up to 8 rows × 4 buttons.

{
  "chat_id": 123,
  "text": "Pick one:",
  "reply_markup": {
    "inline_keyboard": [
      [{ "text": "👍 Like", "callback_data": "like:42" },
       { "text": "👎 Nope", "callback_data": "nope:42" }],
      [{ "text": "🌐 Open site", "url": "https://fdyappa.in" }]
    ]
  }
}

Rich buttons v2

Buttons aren't just text. Give one a style (primary, secondary, danger, success) and an icon, and pick one of four actions:

{
  "text": "Pick a pizza",
  "reply_markup": {
    "inline_keyboard": [
      [{ "text": "Order", "style": "primary", "icon": "checkmark", "callback_data": "order" },
       { "text": "Cancel", "style": "danger",  "callback_data": "cancel" }],
      [{ "text": "📋 Copy promo code", "copy": "FDY-2026" }],
      [{ "text": "👤 Open the chef", "open": { "type": "user", "id": 42 } }]
    ]
  }
}

open.type is an allowlist — user, post, room, live, chat, addressed by numeric id. A bot cannot route someone to a settings screen.

Forms v2

The big one. On Telegram, collecting five fields means five messages and a state machine to remember where each user got to. Here you send one message and get back one form_submission with every answer.

Field types: text, number, select, toggle, rating. Up to 10 per form.

{
  "chat_id": 123,
  "text": "Tell us about yourself",
  "reply_markup": {
    "form": {
      "id": "signup",
      "submit": "Send it",
      "fields": [
        { "key": "name",   "label": "Your name", "type": "text", "required": true },
        { "key": "plan",   "label": "Plan",      "type": "select", "options": ["Free", "Pro"] },
        { "key": "stars",  "label": "Rate us",   "type": "rating", "max": 5 },
        { "key": "notify", "label": "Email me",  "type": "toggle" }
      ]
    }
  }
}

When the user hits submit you receive:

{
  "form_submission": {
    "form_id": "signup",
    "from": { "id": 1, "username": "farhad" },
    "message": { "message_id": 333, "chat": { "id": 57 } },
    "values": { "name": "Farhad", "plan": "Pro", "stars": 5, "notify": true }
  }
}

Values are validated server-side against the form you actually sent. Undeclared fields are dropped, required is enforced, ratings are clamped, and a select only accepts one of its own options — so you can trust values without re-checking it.

Cards v2

A real layout instead of text: an image, a title, a subtitle, and key/value rows. A card can be sent on its own — no caption required — and can carry a keyboard.

{
  "chat_id": 123,
  "reply_markup": {
    "card": {
      "title": "Margherita",
      "subtitle": "Ready in 20 min",
      "image": "https://…/pizza.jpg",
      "fields": [{ "label": "Price", "value": "₹249" }]
    }
  }
}

Inline search v2

POST

/setInline · /answerInlineQuery

A user types @yourbot pizza in any chat — yours or not, group or DM. You get an inline_query update; whatever you answer with, they can tap to send.

Turn it on once with setInline (it's off by default — a bot that never answers would just make the composer wait):

curl -X POST "https://fdyappa.in/bot$TOKEN/setInline" \
  -H "Content-Type: application/json" -d '{"enabled": true}'

Then answer each query. You have 4 seconds — the composer is waiting on you, so be quick:

curl -X POST "https://fdyappa.in/bot$TOKEN/answerInlineQuery" \
  -H "Content-Type: application/json" \
  -d '{
    "inline_query_id": "33b3e435",
    "results": [
      { "id": "1", "title": "Margherita", "subtitle": "Classic", "send": "🍕 Margherita" },
      { "id": "2", "title": "Pepperoni",  "send": "🍕 Pepperoni" }
    ]
  }'

send is what actually gets posted into the chat when the user taps the result. Up to 20 results.

Group moderation v2

A bot that is an admin of a group can moderate it. The server enforces the floor: a bot can only act in groups where it's an admin, and no one — not even an admin bot — can ban, mute, or kick another admin, or itself.

Your bot must still check the person who asked. The server knows the bot is an admin; it does not know whether the user who typed the command is allowed to. Call getChatMember on the sender first — otherwise any member could tell your bot to ban someone.

MethodDoes
banChatMemberRemove + block rejoin. Takes chat_id, user_id (numeric or @username), optional reason.
unbanChatMemberLift a ban.
muteChatMemberThey can't send messages. Optional minutes (0/absent = indefinite, capped at 30 days).
unmuteChatMemberLet them speak again.
purgeChatHistoryDelete messages older than days (≥ 1; never touches system messages).
getChatMemberA user's status (member/administrator/banned/left), is_admin, is_muted.
getChatAdministratorsThe group's admins.
curl -X POST "https://fdyappa.in/bot$TOKEN/muteChatMember" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 57, "user_id": "@spammer", "minutes": 60, "reason": "flood"}'

Want this without writing a bot? @fdyguard is a ready-made one — add it to your group, make it an admin, and type /help.

Answering a button tap

POST

/answerCallbackQuery

When a user taps a callback_data button you receive a callback_query update. Acknowledge it with a short toast (and, if you like, edit the message):

curl -X POST "https://fdyappa.in/bot$TOKEN/answerCallbackQuery" \
  -H "Content-Type: application/json" \
  -d '{"callback_query_id": "a1b2c3", "text": "Liked 👍", "show_alert": false}'

Editing & deleting

POST

/editMessageText · /deleteMessage

A bot can change or remove its own messages. editMessageText takes message_id + new text (and optional reply_markup); deleteMessage takes message_id. Great for live counters, status that updates in place, or self-destructing messages.

Typing indicator — sendChatAction

POST

/sendChatAction

Show a "typing…" indicator in the chat while you prepare a reply. Pass chat_id. It clears on its own or when your next message lands.

The command menu — setMyCommands

POST

/setMyCommands

Register the slash-commands the app shows people when they open your bot. Pass commands: an array of { command, description } (up to 30).

curl -X POST "https://fdyappa.in/bot$TOKEN/setMyCommands" \
  -H "Content-Type: application/json" \
  -d '{"commands":[{"command":"start","description":"Say hi"},{"command":"help","description":"What I can do"}]}'

A complete bot in Node.js

An echo bot with a button — no dependencies, just node bot.js (Node 18+ has global fetch):

const API = `https://fdyappa.in/bot${process.env.TOKEN}`;
const call = (m, body) => fetch(`${API}/${m}`, {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
}).then(r => r.json());

let offset = 0;
async function loop() {
  for (;;) {
    const r = await fetch(`${API}/getUpdates?offset=${offset}&timeout=30`).then(r => r.json());
    for (const u of r.result || []) {
      offset = u.update_id + 1;
      if (u.message) {
        await call('sendMessage', {
          chat_id: u.message.chat.id,
          text: `You said: ${u.message.text}`,
          reply_markup: { inline_keyboard: [[{ text: '👍', callback_data: 'ok' }]] }
        });
      } else if (u.callback_query) {
        await call('answerCallbackQuery', { callback_query_id: u.callback_query.id, text: 'thanks!' });
      }
    }
  }
}
loop();

All methods at a glance

MethodWhat it does
getMeInfo about the bot account.
getChatResolve a chat_id / @username to chat details.
getUpdatesLong-poll for new updates.
setWebhook / deleteWebhook / getWebhookInfoSwitch between webhook and polling.
setMyCommands / getMyCommandsThe slash-command menu.
sendMessageSend text, a form, a card, and/or buttons.
sendPhoto / sendVideoSend media by URL.
editMessageTextRewrite one of the bot's messages.
deleteMessageRemove one of the bot's messages.
answerCallbackQueryAcknowledge a button tap with a toast.
sendChatActionShow a "typing…" indicator.
setInline v2Opt in to inline search (@yourbot query).
answerInlineQuery v2Answer an inline query with tappable results.
banChatMember / unbanChatMember v2Admin bot: ban / unban a group member.
muteChatMember / unmuteChatMember v2Admin bot: stop / allow a member sending.
purgeChatHistory v2Admin bot: delete messages older than N days.
getChatMember / getChatAdministrators v2A member's status; the group's admins.

Update types

UpdateWhen you get it
messageSomeone sent your bot a message.
callback_querySomeone tapped a callback_data button.
form_submission v2Someone submitted a form — every field, one update.
inline_query v2Someone typed @yourbot query. Answer within 4s.
Ready to build? Create your bot in Settings → Bots in the app, then point this loop at your token. Questions: support@fdyappa.in.