Every operation

The 22 operations of PGP Chat Bot API 1.0.0, as the server’s OpenAPI document describes them. All paths are under {apiUrl}/bot/v1. Every call needs the header Authorization: Bearer <token>, and every answer is the envelope {"ok": true, "result": ...} or {"ok": false, "error": {...}}.

The SDK column links to the method that runs the operation. It has the same name on AsyncClient, Client, Bot and SyncBot.

GET /me

Who the bot is

operationId

getMe

Tag

Me

SDK

get_me()

The bot’s own card as the server holds it, plus the limits this server enforces. This works while the bot is paused, so a programme can find out why nothing else does.

Answer

Meaning

200

The bot.

401

unauthorized: missing, revoked or invalid token.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /updates

Fetch updates (long polling)

operationId

getUpdates

Tag

Updates

SDK

get_updates()

Returns the updates waiting for the bot, oldest first, up to limit. With timeout the call hangs until something arrives or the clock runs out, and answers an empty list on the clock.

offset confirms: every update with an updateId below it is deleted and never shown again. Send the highest updateId you handled plus one. Without offset you get everything still in the queue, which is what a bot asks on its very first call.

Only one poll per bot at a time. A second call ends the first one at once with an empty answer, so two copies of the same programme polling one bot will starve each other: run one.

Not available while a webhook is set: delete the webhook first.

Parameter

In

Type

Required

Rules

Description

offset

query

integer

no

min 0

Confirm everything below this id. Highest handled updateId + 1.

limit

query

integer

no

min 1; max 100

At most this many, 1 to 100. Default 100.

timeout

query

integer

no

min 0; max 50

Seconds to wait for something to arrive, 0 to 50. Default 0.

Answer

Meaning

200

Updates, oldest first. Empty when nothing arrived before the timeout.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

409

A webhook is set. Delete it before polling.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /webhook

The webhook, if any, and how it is doing

operationId

getWebhook

Tag

Updates

SDK

get_webhook()

Works while paused.

Answer

Meaning

200

Webhook state.

401

unauthorized: missing, revoked or invalid token.

429

rate_limited: too many calls. Wait retryAfter seconds.

POST /webhook

Set a webhook

operationId

setWebhook

Tag

Updates

SDK

set_webhook()

From now on every update is POSTed as JSON to url, one update per request, in order, with the header User-Agent: PGPChat-Bot-Api/1. If you give a secret, every delivery carries it in the header X-PGPChat-Bot-Api-Secret-Token: check it and refuse anything without it.

Answer with any 2xx within 10 seconds and the update is considered delivered. Anything else, a timeout or a redirect counts as a failure: the server backs off (one second, then doubling, up to an hour) and tries that same update again, so delivery is at least once and never out of order. After ten failures in a row the owner is told in the app. Updates still queue while the webhook is failing and are kept for 72 hours.

The URL must be https, carry no credentials or fragment, and resolve to a public address. Setting a webhook ends any poll in progress; GET /updates refuses while one is set.

Request body (JSON, required):

Field

Type

Required

Rules

Description

url

string

yes

max length 1024

Absolute https URL.

secret

string

no

pattern ^[A-Za-z0-9_-]{1,256}$

Sent back on every delivery in X-PGPChat-Bot-Api-Secret-Token.

Answer

Meaning

200

Set.

400

The URL is not acceptable (bad_webhook) or the body is wrong (bad_request).

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

429

rate_limited: too many calls. Wait retryAfter seconds.

DELETE /webhook

Remove the webhook

operationId

deleteWebhook

Tag

Updates

SDK

delete_webhook()

Back to polling. Updates that queued while the webhook was set are still there. Works while paused.

Answer

Meaning

200

Removed.

401

unauthorized: missing, revoked or invalid token.

429

rate_limited: too many calls. Wait retryAfter seconds.

PUT /key

Publish the bot’s OpenPGP public key

operationId

setKey

Tag

Key

SDK

set_key()

Turns the bot protected: from now on its private chats are end-to-end encrypted, people’s phones seal every message to this key, and the bot must send armored into those chats. Calling it again with a new key is a rotation; every phone learns the new fingerprint at once.

There is no way to remove the key from the API on purpose: downgrading a bot from protected to open is a decision for its owner, in the console.

Request body (JSON, required):

Field

Type

Required

Rules

Description

publicKey

string

yes

max length 65536

ASCII-armoured OpenPGP public key block.

fingerprint

string

yes

pattern ^[0-9A-Fa-f]{40}$|^[0-9A-Fa-f]{64}$

The key’s fingerprint, 40 (v4) or 64 (v6) hex characters.

Answer

Meaning

200

Published.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /commands

The command lists

operationId

getCommands

Tag

Commands

SDK

get_commands()

Every scope with its commands, or one scope’s list when scope is given.

Parameter

In

Type

Required

Rules

Description

scope

query

string

no

pattern ^(default|private|groups|groupAdmins|chat:\d{1,12})$

Answer

Meaning

200

All scopes (no scope), or one list.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

429

rate_limited: too many calls. Wait retryAfter seconds.

PUT /commands

Replace the commands of one scope

operationId

setCommands

Tag

Commands

SDK

set_commands()

The whole list of that scope, replaced. Up to 100 commands; a command is 1 to 32 lowercase letters, digits and underscores (a leading slash is dropped), its description 1 to 256 characters. Duplicates are folded to the first.

Scopes resolve the way Telegram’s do, narrowest first: in a private chat private then default; in a group chat:<id>, then groupAdmins for administrators, then groups, then default. An empty scope does not hide a wider one. The owner can edit the same lists in the console; whichever wrote last wins.

Request body (JSON, required):

Field

Type

Required

Rules

Description

scope

string

no

pattern ^(default|private|groups|groupAdmins|chat:\d{1,12})$

default, private, groups, groupAdmins, or chat:<id> for one chat the bot is in. Missing means default.

commands

array of object

yes

Answer

Meaning

200

Saved.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

A chat:<id> scope names a chat the bot is not in.

429

rate_limited: too many calls. Wait retryAfter seconds.

DELETE /commands

Empty one scope

operationId

deleteCommands

Tag

Commands

SDK

delete_commands()

Parameter

In

Type

Required

Rules

Description

scope

query

string

no

pattern ^(default|private|groups|groupAdmins|chat:\d{1,12})$

Answer

Meaning

200

Emptied.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

429

rate_limited: too many calls. Wait retryAfter seconds.

POST /messages

Send a message

operationId

sendMessage

Tag

Messages

SDK

send_message()

Into a chat the bot is in. Send text into a readable chat and armored into an encrypted one; the wrong kind is refused with wrong_body_kind and expected says which. A markup adds rows of buttons under the message. replyTo quotes a message of the same chat (a wrong id is silently dropped). silent only matters in a channel: the post goes out without a notification.

sendId makes the call safe to repeat: a UUID of your choosing, and a second call with the same one hands back the message already written instead of writing it twice. Use it whenever you retry after a timeout.

In an encrypted chat, seal the message to every key GET /chats/{id}/keys returns and to the bot’s own key, sign it with the bot’s key, and send the armoured result.

Request body (JSON, required):

Field

Type

Required

Rules

Description

chatId

integer

yes

text

string

no

min length 1; max length 4096

Readable chats. No control characters apart from newline and tab.

armored

string

no

max length 131072

Encrypted chats. An ASCII-armoured OpenPGP message.

replyTo

integer or null

no

markup

object

no

Rows of buttons under a message. Up to 20 rows, 8 buttons a row, 100 in all, 8 KiB of JSON. The buttons are not encrypted: the server has to know which one was pressed.

silent

boolean

no

Channels only: no notification.

sendId

string

no

format uuid

Your id for this send, so a retry writes nothing twice.

Answer

Meaning

201

Sent. A repeated sendId answers 200 with the message already written instead of writing it twice.

400

bad_request, wrong_body_kind (with expected) or bad_markup.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused, user_gone (the person closed their account), blocked_by_user, or forbidden (no right to write here).

404

chat_not_found: no such chat, or the bot is not in it.

409

conflict: this sendId was already used in another chat.

429

flood_wait (sending too fast), slow_mode (the group’s slow mode) or rate_limited. Wait retryAfter seconds.

PATCH /messages/{messageId}

Edit one of the bot’s messages

operationId

editMessage

Tag

Messages

SDK

edit_message()

New words, new buttons, or both. Only the bot’s own ordinary messages, at any age: a scoreboard that changes for a week is the normal use. Service notices the bot caused (“left the group”) are not its to edit. Leave markup out to keep the buttons, send markup: null to remove them. People in the chat see the edit live. Edits are counted against the same numbers as sends, in a window of their own: one a second in a private chat, twenty a minute in a group, and the thirty a second overall is shared.

Parameter

In

Type

Required

Rules

Description

messageId

path

integer

yes

Request body (JSON, required):

Field

Type

Required

Rules

Description

chatId

integer

yes

text

string

no

max length 4096

armored

string

no

max length 131072

markup

object or null

no

Omit to keep the buttons, null to remove them.

Answer

Meaning

200

Edited.

400

bad_request (nothing to change, or a field is wrong), wrong_body_kind (text into an encrypted chat or armored into a readable one, see expected) or bad_markup.

401

unauthorized: missing, revoked or invalid token.

403

forbidden: not the bot’s message, or not an ordinary one. In a private chat blocked_by_user or user_gone. bot_paused.

404

chat_not_found or message_not_found.

429

rate_limited: too many calls. Wait retryAfter seconds.

DELETE /messages/{messageId}

Delete a message

operationId

deleteMessage

Tag

Messages

SDK

delete_message()

The bot’s own messages anywhere; anybody’s in a group or channel where the bot holds the delete messages right. Secret messages and service notices cannot be deleted by a bot. chatId goes in the JSON body or in the query.

Parameter

In

Type

Required

Rules

Description

messageId

path

integer

yes

chatId

query

integer

no

The chat, when not sent in the body.

Request body (JSON):

Field

Type

Required

Rules

Description

chatId

integer

no

Answer

Meaning

200

Deleted for everyone, for good. deleted is always true here: a message that is already gone answers 404 message_not_found, so a retry after a timeout can treat that 404 as done.

401

unauthorized: missing, revoked or invalid token.

403

forbidden or bot_paused.

404

chat_not_found or message_not_found.

429

rate_limited: too many calls. Wait retryAfter seconds.

POST /callbacks/{callbackId}/answer

Answer a button press

operationId

answerCallback

Tag

Buttons

SDK

answer_callback()

When somebody presses a button with callbackData, the bot gets a callback_query update and the button spins on their phone. Answer within 60 seconds: with text a short line flashes over the chat; with alert: true the text is shown in a box they have to dismiss; with url their phone shows the address and offers to open it. An empty answer just stops the spinner. The message itself changes only if you edit it (PATCH /messages/{id}).

Each press is answered once. A second press on the same message while the first is open is the same press.

Parameter

In

Type

Required

Rules

Description

callbackId

path

string

yes

Request body (JSON):

Field

Type

Required

Rules

Description

text

string

no

max length 200

alert

boolean

no

default false

url

string

no

max length 1024

Absolute http(s) URL without credentials.

Answer

Meaning

200

Delivered to the phone that pressed.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

callback_not_found: no such press for this bot.

409

callback_answered: already answered.

410

callback_expired: more than 60 seconds have passed.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /chats/{chatId}

A chat the bot is in

operationId

getChat

Tag

Chats

SDK

get_chat()

Its kind, its mode (which body to send), and for a group the bot’s own seat. A private chat carries the other person and whether they blocked the bot.

Parameter

In

Type

Required

Rules

Description

chatId

path

integer

yes

A chat the bot is in. Any other id answers chat_not_found.

Answer

Meaning

200

The chat.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

chat_not_found: no such chat, or the bot is not in it.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /chats/{chatId}/keys

The keys to seal a message to

operationId

getChatKeys

Tag

Chats

SDK

get_chat_keys()

For an encrypted chat: the public keys of everybody in it, ids and keys only, no names. Seal your message to all of them and to the bot’s own key. Other bots that only receive what is addressed to them are left out, as they are for the app. A readable chat has no keys to give and answers chat_is_open.

Parameter

In

Type

Required

Rules

Description

chatId

path

integer

yes

A chat the bot is in. Any other id answers chat_not_found.

Answer

Meaning

200

Keys.

400

chat_is_open: send text, no keys are needed.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

chat_not_found, or no_user_key in a private chat whose person has no key.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /chats/{chatId}/members

The roster of a group or channel

operationId

getChatMembers

Tag

Chats

SDK

get_chat_members()

Who is in it and with which seat, as a member in the bot’s seat would see it: where the roster is hidden a bot that is not an admin sees the admins only, and a channel that keeps its admins to itself leaves them out. Names and handles only, never phone numbers. memberCount counts the list as returned.

Parameter

In

Type

Required

Rules

Description

chatId

path

integer

yes

A chat the bot is in. Any other id answers chat_not_found.

Answer

Meaning

200

Members.

400

A private chat has no members.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

chat_not_found: no such chat, or the bot is not in it.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /chats/{chatId}/members/{userId}

One member’s seat

operationId

getChatMember

Tag

Chats

SDK

get_chat_member()

Parameter

In

Type

Required

Rules

Description

chatId

path

integer

yes

A chat the bot is in. Any other id answers chat_not_found.

userId

path

integer

yes

Answer

Meaning

200

The member.

400

A private chat has no members.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

chat_not_found or member_not_found.

429

rate_limited: too many calls. Wait retryAfter seconds.

POST /chats/{chatId}/leave

Leave a group or channel

operationId

leaveChat

Tag

Chats

SDK

leave_chat()

The room is told, the way it would be about a person. A bot cannot leave a private chat; the person can block it.

Parameter

In

Type

Required

Rules

Description

chatId

path

integer

yes

A chat the bot is in. Any other id answers chat_not_found.

Answer

Meaning

200

Left.

400

A private chat cannot be left.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

chat_not_found: no such chat, or the bot is not in it.

429

rate_limited: too many calls. Wait retryAfter seconds.

POST /chats/{chatId}/typing

Show “typing” for a few seconds

operationId

sendTyping

Tag

Chats

SDK

send_typing()

The same indicator a person’s phone sends. It clears on its own after a few seconds or when a message lands; send typing: false to clear it early. Only where the bot may write: a restricted bot, or a channel subscriber, gets 403 forbidden. One goes out per chat every 3 seconds at most; calls in between answer sent: false and show nothing.

Parameter

In

Type

Required

Rules

Description

chatId

path

integer

yes

A chat the bot is in. Any other id answers chat_not_found.

Request body (JSON):

Field

Type

Required

Rules

Description

typing

boolean

no

default true

Answer

Meaning

200

sent is false when one went out to this chat less than 3 seconds ago.

401

unauthorized: missing, revoked or invalid token.

403

forbidden: the bot may not write in this chat. bot_paused.

404

chat_not_found: no such chat, or the bot is not in it.

429

rate_limited: too many calls. Wait retryAfter seconds.

GET /resolve/{username}

A public handle, turned into an id

operationId

resolve

Tag

Names

SDK

resolve()

For the handle a person types at your bot - “paga @anna”, “segui @notizie” - when no message has handed you that id. People, groups and channels share one pool of names, so a handle is held by exactly one of the three and kind says which.

The answer carries id at the top, and the same thing again under user or chat with what little goes with it. username is the name that account or chat actually goes by, which is not always the one you asked with: a person can answer to several handles.

It finds anybody with a public handle, which is no more than the short link already tells a stranger with a browser (pgpchat.me/anna). The id on its own grants nothing: sending still needs the bot to be in the chat, and a key still needs a chat in common. Out of reach: a chat with no public name (a private group is not in the pool at all), an account that has been closed, and the server’s own service account.

Harder limit than the rest of the API: 60 a minute, and a handle nobody holds counts as five, so about a dozen blind guesses spend the minute. Over it you get 429 flood_wait with retryAfter.

Parameter

In

Type

Required

Rules

Description

username

path

string

yes

pattern ^[A-Za-z0-9_]{3,32}$

A public handle, without the @. 3-32 characters: letters, digits and underscore. Case does not matter.

Answer

Meaning

200

What the handle turned out to be.

400

bad_request: the body or the query is wrong; message says which field.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

username_not_found: nobody and no chat answers to that name.

429

flood_wait: too many lookups, wait retryAfter seconds. rate_limited.

GET /users/{userId}/key

One person’s public key

operationId

getUserKey

Tag

Chats

SDK

get_user_key()

Only for somebody the bot shares a chat with; anybody else is user_not_found, whether or not they exist.

Parameter

In

Type

Required

Rules

Description

userId

path

integer

yes

Answer

Meaning

200

The key.

401

unauthorized: missing, revoked or invalid token.

403

bot_paused: the owner paused the bot. Only GET /me, GET /webhook, DELETE /webhook and POST /token/revoke work meanwhile.

404

user_not_found or no_user_key.

429

rate_limited: too many calls. Wait retryAfter seconds.

POST /token/revoke

Burn this token

operationId

revokeToken

Tag

Token

SDK

revoke_token()

For a programme that notices its token leaked. Nothing works again, this call included, until the owner issues a new token in the console; the owner is told at once. Works while paused.

Answer

Meaning

200

Revoked.

401

unauthorized: missing, revoked or invalid token.

429

rate_limited: too many calls. Wait retryAfter seconds.

Webhook: update

An update, delivered to your webhook

One update per request, in order. Answer any 2xx within 10 seconds. The header X-PGPChat-Bot-Api-Secret-Token carries the secret you set, if any; User-Agent is PGPChat-Bot-Api/1. Deliveries are at least once: a slow 2xx can be retried, so treat updateId as the thing that makes a delivery new.

The body is one Update. In the SDK: create_webhook_app().

Answer

Meaning

200

Any 2xx: delivered. Anything else, a timeout or a redirect: retried later.