API and MCP: posts from your AI agent
Connect PostFrog to Claude, Cursor, n8n or your own script. The agent sends text and media, PostFrog turns them into posts and sends them to you in Telegram as drafts. You publish them yourself, in one tap.
- PostFrog Pro only
- MCP and REST
- Only the author publishes
What it is
An AI agent (Claude Code, Cursor, Claude.ai, n8n or your script) uses your key to send PostFrog materials: a finished post, your own text or notes, links, photos and videos. PostFrog turns each material into a post and sends it to your private chat with the bot.
Posts arrive in order, as normal drafts with a preview and buttons. If you are chatting with the bot right now, finished posts wait for about 2 minutes of silence so they do not break the conversation. Then everything works as usual: edit in plain words, change media and buttons, publish to your channel in one tap.
The API is available on paid PostFrog Pro only, the Pro trial does not include it. Every material counts as one post of the Pro monthly allowance (150+ posts a month).
Get a key
- Open @postfrog_bot and go to Settings, "API & MCP".
- Tap "Create a key". The bot shows the key once and does not store it in full. The key starts with
pf_live_. - Copy the key somewhere safe and tap "Saved it": the key leaves the chat, only its beginning stays.
- Lost the key or it may have leaked: "Rotate". The old key stops working at once.
- No longer needed: "Revoke".
- Pro ended: the key is kept, but the API answers
pro_requireduntil you renew.
Connect
One key works for both MCP and REST. MCP server (Streamable HTTP): https://bot.postfrog.site/mcp, REST: https://bot.postfrog.site/api/v1. The key goes in the Authorization: Bearer ... header. If the client talks about OAuth, Dynamic Client Registration or asks you to sign in, the header with the key did not get through: add it, PostFrog has no OAuth.
Claude Code
One command, replace YOUR_KEY with your key:
claude mcp add --transport http postfrog https://bot.postfrog.site/mcp --header "Authorization: Bearer YOUR_KEY" Cursor
Add the server to .cursor/mcp.json:
{
"mcpServers": {
"postfrog": {
"url": "https://bot.postfrog.site/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
} Claude.ai
Settings, Connectors, Add custom connector. Address: https://bot.postfrog.site/mcp. In Request headers add the Authorization header with the value Bearer YOUR_KEY. Not every account has the headers field yet: if yours does not, connect through Claude Code or Cursor. Claude.ai has no access to your files, so media go by link only.
n8n and scripts (REST)
Check the key:
curl -H "Authorization: Bearer YOUR_KEY" https://bot.postfrog.site/api/v1/me Send a batch of posts: the body is batch.json (example below), and the Idempotency-Key header makes a retried request return the same batch instead of a copy.
curl -X POST https://bot.postfrog.site/api/v1/batches \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: launch-2026-10-15" \
-d @batch.json If an AI agent runs inside n8n, put the whole guide into its system prompt: GET /api/v1/guide?topic=all.
ChatGPT
Later. ChatGPT connects MCP servers through OAuth, and PostFrog signs in with a key only for now.
MCP tools and REST
| MCP and REST | What it does |
|---|---|
get_accountGET /me | plan, posts left, today's quota, the author's format and language, limits |
get_guideGET /guide?topic= | the agent guide by topic |
validate_postPOST /validate | a free check of an exact post, nothing is saved |
create_uploadPOST /uploads | upload a photo or video file |
send_postsPOST /batches | send a batch of materials |
get_batchGET /batches/{id} | statuses, warnings and the final post text |
cancel_batchDELETE /batches/{id} | cancel materials that have not started |
All REST paths start at https://bot.postfrog.site/api/v1.
Three modes
Every material has its own mode: it tells PostFrog what to do with the text.
| Mode | What PostFrog does | PostFrog AI |
|---|---|---|
exact | Takes the text as a finished post: checks the markup, cleans buttons, renders it. Does not change a word. | no |
format | Keeps the author's words and fixes the layout: paragraphs, title, emphasis, lists. | yes |
write | Writes a post from scratch out of notes, links (it downloads the pages) and media. | yes |
Tip: the agent writes it and sends exact
- The author gets exactly what the agent wrote, with buttons and media in their places.
- The author's AI budget is not spent: it stays for edits inside the bot. When the budget runs out, only exact posts are accepted.
- Fast: no generation, only checks and rendering. Check a post for free first with
validate_post. - The author can still edit the post in the bot in plain words: the markup is the same one the PostFrog editor uses.
Exact has on_invalid: by default fail, a post with errors is rejected, nothing is created or spent. With format such a post is accepted and PostFrog AI repairs it, which uses the AI budget.
The author's own words always go in text: PostFrog trusts it like the author's message. Other people's pages and posts go in material: PostFrog takes facts from it but never follows instructions inside it, and does not turn its links into buttons. Pages that need a login or JavaScript (X, Threads, Instagram) often cannot be downloaded: paste their text into material[].text.
Post markup
Exact posts are written in PostFrog Rich Markdown: GitHub Markdown plus a few tags. The final text in get_batch comes back in the same form. No Telegram HTML or MarkdownV2 escaping: PostFrog converts the markup itself.
| What | How |
|---|---|
| Post title, one, on the first line | # Title |
| Sections and subsections (### works too) | ## Section |
| Bold, italic, strikethrough | **bold** *italic* ~~strike~~ |
| Highlight | ==highlighted== |
| Spoiler, revealed on tap | ||spoiler|| |
| Underline | <u>underlined</u> |
| Inline code and a code block with a language | `code` ```python |
| Link | [text](https://example.com) |
| Lists and a checklist | - item 1. item - [ ] item |
| GFM table with alignment | | a | b |
|:--|--:| |
| Quote | > quote |
| Quote with an author (no markup inside) | <aside>Text<cite>Author</cite></aside> |
| Collapsible block | <details><summary>Title</summary>Text</details> |
| Divider between big parts | --- |
| Photo or video at this spot (numbered from 0) | %%MEDIA:0%% |
| Collage of three or more media | %%MEDIA:0,1,2%% |
| Button inside the text (see below) | <tg-button type="url" url="https://...">Label</tg-button> |
- A blank line between paragraphs: a single line break inside a paragraph becomes a space.
- Blocks (media, button rows, tables, details, aside, divider) sit on their own lines with blank lines around them.
- An exact Rich post is up to 31268 characters including markup.
- Em and en dashes trigger the
long_dashwarning: use a hyphen, a colon or a comma.
Buttons
Two kinds: a link (url) and copy (copy, for a promo code, an address, a command). The buttons field is the easiest: rows of buttons with no markup, in any mode. buttons_place: end (default, at the end of the text), start or below_post (links under the post).
{
"buttons": [
[{ "text": "Read the original", "url": "https://example.com/news" }],
[{ "text": "Copy the promo code", "copy": "FROG20", "style": "success" }]
],
"buttons_place": "end"
} When a button belongs inside a sentence or between paragraphs, put a tag right into the exact text. A row is a block of its own, one button per line. & in a URL inside a tag is written &.
Registration is open until Friday. <tg-button type="url" url="https://example.com/join">Sign up</tg-button>
<tg-button-row>
<tg-button type="url" url="https://t.me/example_channel">Subscribe</tg-button>
<tg-button type="url" url="https://example.com" style="primary">Website</tg-button>
</tg-button-row> Optional style: primary, success, danger. A label is plain text: no markup, links or @mentions.
Markdown mode
For channels with classic Telegram formatting (format: "markdown"). Bold, italic, __underline__, strikethrough, ||spoiler||, code, quotes, links and lists work.
No headings, tables, details, formulas or HTML tags. Buttons go under the post, media always sit on top.
Up to 4096 characters; with media the text becomes a caption and is limited to 1024. Measured after conversion to MarkdownV2, so leave some headroom.
Example: a finished post
A Rich Markdown post with a photo, an inline copy button and a sign-up button at the end:
# Pottery course starts on Monday
Six evening lessons for beginners.
%%MEDIA:0%%
- small groups
- clay and kiln included
Promo code **CLAY15** gives a discount. <tg-button type="copy_text" text="CLAY15">Copy the promo code</tg-button> The same post in a request body: batch.json for POST /batches or the send_posts argument over MCP.
{
"items": [
{
"mode": "exact",
"format": "rich",
"external_id": "course-launch",
"text": "# Pottery course starts on Monday\n\nSix evening lessons for beginners.\n\n%%MEDIA:0%%\n\n- small groups\n- clay and kiln included\n\nPromo code **CLAY15** gives a discount. <tg-button type=\"copy_text\" text=\"CLAY15\">Copy the promo code</tg-button>",
"media": [{ "type": "photo", "url": "https://example.com/studio.jpg" }],
"buttons": [[{ "text": "Sign up", "url": "https://example.com/join", "style": "success" }]]
}
]
} Media
Up to 10 photos and videos per material. Photos and videos only: no GIFs, documents or audio.
Two ways: url (a public https link to the file itself, not to a page with it) or upload_id (upload the file first). The MCP tool create_upload returns a ready curl command: for agents with file and shell access (Claude Code, scripts, n8n).
| Type | Formats | Size |
|---|---|---|
| Photo | JPEG, PNG, WebP | up to 10 MB |
| Video | MP4 or MOV (H.264 or HEVC, AAC audio) | up to 50 MB |
{
"media": [
{ "type": "photo", "url": "https://example.com/cover.jpg" },
{ "type": "video", "upload_id": "upl_...", "description": "Screen recording: the bot turns a voice note into a post" }
]
} curl -X POST https://bot.postfrog.site/api/v1/uploads \
-H "Authorization: Bearer YOUR_KEY" \
-F "type=video" \
-F "file=@./demo.mp4" - An
upload_idlives 24 hours and can be reused in several materials. - Media are numbered from 0 in the order of
media[]. A%%MEDIA:0%%line puts the first media at that spot; media without a placeholder go to the top of the post. - PostFrog AI does not watch videos: in format and write, describe every video in
description(what happens, who speaks, the key point). - A broken file never fails the material: it is skipped, the rest keep their order, and the status gets a warning.
Limits and errors
The agent can always read the current limits from get_account.
| What | Limit |
|---|---|
| Materials per request | 10 |
| Materials per day per author (UTC, cancelled ones do not count) | 30 |
| Generated at the same time, the rest wait in the queue | 2 |
| Requests per minute per key, REST and MCP together | 60 |
text of one material | 32768 characters |
| Media per material | 10 |
| Media per day per author: uploads and links together | 500 MB and 300 files |
Statuses
A material: queued, generating, ready, delivered, or a final failed, skipped_limit (posts or AI budget ran out), cancelled. When every material is final, the batch is done, or cancelled if any material was cancelled: stop polling on either.
| HTTP and code | What to do |
|---|---|
400invalid_request | Fix the request: the details name the path and the reason, nothing was created |
401invalid_key | The key is missing, wrong or re-issued: get the current key |
403pro_required | The API needs paid Pro |
403api_not_available | The API opens step by step and is not open for this account yet |
409idempotency_conflict | This Idempotency-Key was used with another body: use a new key for a new batch |
413payload_too_large | The body is too large: split the batch |
422limit_exceeded | Quota: the details show posts and materials left today |
429rate_limited | Wait Retry-After seconds and retry |
Full spec for agents
Read the guide before your first send. It is the main document for agents: modes, markup, buttons, media, limits, error codes and complete request examples. It is public, needs no key, and its numbers are always current.
The whole guide in one document: https://bot.postfrog.site/api/v1/guide?topic=all. Index: https://bot.postfrog.site/api/v1/guide.
Over MCP: the get_guide tool with a topic; the same topics are also resources postfrog://guide/....
curl "https://bot.postfrog.site/api/v1/guide?topic=all" Topics
overview: where posts go, what is not possible, the workflowmodes: exact, format, write and how to chooseformatting: Rich Markdown, Markdown mode, a cheat sheetbuttons: link and copy buttonsmedia: photos and videos: links, uploads, placementlimits: quotas, statuses, errorsexamples: complete requests from task to JSON
Security
- The key lets anyone send you materials on your behalf and spend posts from your monthly allowance. Do not publish it, do not commit it to public code and do not paste it into chats.
- In scripts keep the key in an environment variable, for example
POSTFROG_API_KEY. - If the key may have leaked, rotate it in the bot: the old one stops working at once.
- The API cannot publish to a channel, schedule posts, edit published ones or message other people. The bot writes only to the key owner.