# Telegram photo post API

Post a photo with a caption to a Telegram channel or group through an API or MCP server: the 5 MB and 1,024-character limits, example requests, and errors with fixes.

**Beta:** you can connect a Telegram account and post to it through the API in early access today. It hasn't been proven with a real production post yet. The `@turboyap/sdk` and `@turboyap/mcp` packages call the same API. [Test mode](https://turboyap.com/docs/test-mode) runs the same requests against a simulated Telegram account.

## What a Telegram photo post is

A Telegram photo post is one image with a caption of up to 1,024 characters, sent to a channel or group with the Bot API's `sendPhoto`. In Turboyap it's `type: "image"` with one media id.

Turboyap hosts the image, converts and resizes it to fit Telegram's limits, and Telegram fetches it from Turboyap's media URL when the post goes out.

## Limits

What Telegram accepts for this post type. Turboyap's planner checks every one of these before a post is scheduled (the same data as [platform rules](https://turboyap.com/docs/platform-rules)), and each value links to the Telegram documentation it comes from.

| Limit | Value | Source |
|---|---|---|
| Images per post | 1 | [Telegram Bot API: sendPhoto](https://core.telegram.org/bots/api#sendphoto) |
| Formats | JPEG, PNG, WebP (Turboyap converts anything else to JPEG) | [Telegram Bot API: sendPhoto](https://core.telegram.org/bots/api#sendphoto) |
| File size | 5 MB (5,000,000 bytes) per image | [Telegram Bot API: sending files](https://core.telegram.org/bots/api#sending-files) |
| Dimensions | Fits 2560×2560 (Turboyap resizes to fit) | [Telegram Bot API: sendPhoto](https://core.telegram.org/bots/api#sendphoto) |
| Aspect ratio | 1:20 to 20:1 | [Telegram Bot API: sendPhoto](https://core.telegram.org/bots/api#sendphoto) |
| Caption | 1,024 characters (sent as `caption`) | [Telegram Bot API: sendPhoto](https://core.telegram.org/bots/api#sendphoto) |
| Link card | No: `link` is ignored on Telegram photo posts, with a warning | [Telegram Bot API: sendMessage](https://core.telegram.org/bots/api#sendmessage) |
| Rate limit | About 1 message per second in a chat, and 20 per minute in a group | [Telegram bots FAQ: hitting limits](https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this) |
| Rate limit | On a flood-control error Turboyap waits the retry_after Telegram sends | [Telegram bots FAQ: hitting limits](https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this) |

Compare with other platforms in the [social media API limits tracker](https://turboyap.com/tools/social-api-limits#telegram).

## Connect a Telegram account

1. In Telegram, open your channel or group, then **Administrators**, then **Add Admin**, and choose the Turboyap bot. The connect page and `GET /v1/connect_methods` show its exact @username.
2. Give it permission to post messages (channels) or send messages (groups). It needs nothing else.
3. Connect the channel's public @username or t.me link. For a private channel, use its numeric chat ID: open it in Telegram Web (web.telegram.org/a) and copy the number after # in the address bar. It starts with -100.

Turboyap checks that the bot can post there before it saves the account. A human with a live key can connect directly with `POST /v1/accounts` and `{ "platform": "telegram", "chat": "@yourchannel" }`. Turboyap posts to channels and groups, not to private chats with a person.

An agent never handles the account's credentials: it calls the `create_connect_link` MCP tool (or `POST /v1/connect_links` with the platform) and sends the 7-day link to the person who owns the account, whose page walks them through these steps. New accounts start on autonomy level 1, so agent posts wait for approval.

## Example requests

All three do the same thing: upload the media, preview the post with a dry run, then schedule it. The idempotency key makes retries safe: send the same request again with the same key and you get the original post back, not a second one.

**Early access:** `api.turboyap.com` is open to early-access users only, and `@turboyap/sdk` and `@turboyap/mcp` aren't on npm yet. These examples show the API as built.

### curl

```bash
# 1. Upload each image. The body is the raw file; Content-Type is its type.
curl https://api.turboyap.com/v1/media \
  -H "Authorization: Bearer $TURBOYAP_API_KEY" \
  -H "Content-Type: image/png" \
  -H "X-Filename: slide-1.png" \
  --data-binary @slide-1.png
# → { "media": { "id": "med_…", "width": 1080, "height": 1920, … }, "deduplicated": false }

# 2. Preview. Remove "dry_run" to create and schedule the post.
curl https://api.turboyap.com/v1/posts \
  -H "Authorization: Bearer $TURBOYAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "image",
    "media_ids": ["med_…"],
    "caption": "Read the label before you buy.",
    "targets": [{ "account_id": "acct_telegram…" }],
    "schedule_at": "2026-10-20T18:00:00-04:00",
    "fit": { "aspect": "pad" },
    "idempotency_key": "telegram-photo-2026-10-20",
    "dry_run": true
  }'
```

### TypeScript with @turboyap/sdk

```ts
import { readFile } from "node:fs/promises";
import { TurboyapClient } from "@turboyap/sdk";

const ty = new TurboyapClient({ apiKey: process.env.TURBOYAP_API_KEY! });
const account = (await ty.listAccounts()).find((a) => a.platform === "telegram")!;

const media_ids: string[] = [];
for (const file of ["image.png"]) {
  const { media } = await ty.uploadMedia(await readFile(file), { filename: file, contentType: "image/png" });
  media_ids.push(media.id);
}

const post = {
  type: "image" as const,
  media_ids,
  caption: "Read the label before you buy.",
  targets: [{ account_id: account.id }],
  schedule_at: "2026-10-20T18:00:00-04:00",
  fit: { aspect: "pad" as const },
  idempotency_key: "telegram-photo-2026-10-20",
};

// Preview first: every problem comes back with a fix.
const preview = await ty.createPost({ ...post, dry_run: true });
if (!preview.ok) throw new Error(preview.errors.map((e) => `${e.code}: ${e.fix}`).join("\n"));

const { post: created } = await ty.createPost(post);
console.log(created.targets[0]?.status, created.targets[0]?.next_step);
```

### Claude Code (MCP)

Add the Turboyap MCP server once, with an agent key from your workspace:

```bash
claude mcp add turboyap -e TURBOYAP_API_KEY=ty_agent_… -- npx -y @turboyap/mcp
```

Then ask:

> Post ~/content/label-check.png to our Telegram channel tomorrow at 8am ET with the caption "Read the label before you buy." Dry-run first.

The agent calls `list_accounts`, `get_platform_rules`, `upload_media`, `create_post` with `dry_run: true`, applies any fixes, calls `create_post` again to schedule, and reports each account's `next_step` from `get_post`. The same server works in Cursor; see the [MCP quickstart](https://turboyap.com/docs/mcp-quickstart).

## How publishing works

When the scheduled time arrives, Turboyap's bot sends the post with `sendPhoto`, and Telegram fetches the photo from Turboyap's media URL. Publishing is immediate: the target moves `scheduled` → `publishing` → `published`. `platform_url` links to the message in channels and supergroups; basic groups have no message links. Telegram's Bot API has no idempotency key, so if a response is lost Turboyap marks the target `unknown_outcome` instead of posting again.

Text posts take 4,096 characters; captions on photos and albums take 1,024.

Pass `link` on a text post to choose which URL gets the link preview.

Video and documents aren't supported yet: Turboyap's media pipeline takes images only.

## Common errors and fixes

Every error has a `code`, the `field` that caused it, and a `fix` an agent can apply without asking you. A request with several problems returns all of them in `issues`; a dry run returns them without creating anything.

| Code | When | Fix |
|---|---|---|
| `too_many_items` | More than one media id on an image post. | Send one media id, or set `fit.overflow` to `"truncate"` to post only the first. For several images, use `type: "slideshow"`. |
| `aspect_ratio_out_of_range` | An image is outside 1:20 to 20:1 and `fit.aspect` is `"strict"` (the default). | Set `fit.aspect` to `"pad"` to letterbox (keeps all text visible) or `"crop"` to center-crop. |
| `caption_too_long` | Caption over 1,024 characters. | Shorten it, or pass `targets[i].caption` with a version under 1,024 characters. |
| `invalid_request` | `link` on Telegram photo posts. It's a warning, not an error: the post goes out without the card. | Put the URL in the caption instead, or post it as a text post. |
| `media_format_unsupported` | A media id points to a video, or the upload isn't an image Turboyap can read. | Upload PNG, JPEG or WebP images. Video posts are coming soon. |
| `schedule_in_past` | `schedule_at` is more than a minute in the past. | Use `"now"` or a future time with a timezone offset. |
| `account_unavailable` | The Telegram account needs to be reconnected. | Reconnect the account, then retry. |
| `unsupported_platform` | A target is on a platform Turboyap doesn't support yet. | Remove that target. Supported platforms: tiktok, instagram, bluesky (beta), telegram (beta), discord (beta). |
| `insufficient_scope` | The key lacks a scope the request needs, such as `publish` for `schedule_at: "now"` (agent keys don't have it by default). | Schedule a future time instead of `"now"`, omit `schedule_at` to save a draft, or use a key with the missing scope. `GET /v1/keys/me` lists the key's scopes. |
| `account_not_allowed` | The key is limited to some accounts, and a target isn't one of them. | Remove that target, or use a key whose `account_ids` include the account. |
| `forbidden_scope` | An agent key tries to approve or reject a post. | A human approves agent posts. To skip review for an account, a human sets its autonomy level to 3. |
| `unknown_outcome` | Turboyap stopped partway through publishing (for example, a restart) and can't confirm whether the post went out. It doesn't publish it again. | Check the account on the platform. If the post isn't there, create it again. |

Errors from Telegram itself appear on the target's `error` with Telegram's own code. Turboyap retries rate limits and temporary failures with backoff, and marks the account for reconnection when the bot can no longer post to the chat.

## Approval and audit

- **Level 1, review everything (default):** a post an agent schedules starts as `pending_approval` and waits until a human approves it with a human key (`POST /v1/posts/:id/approve`). Agent keys can't approve.
- **Level 3, full autopilot:** agent posts schedule directly. Every action is still logged. Level 2, guardrails, is coming soon.
- **Posts created by humans** schedule directly at any level.
- **Edits re-open review:** if an agent changes the caption or time of an approved post on a level-1 account, it goes back to `pending_approval`.
- **Dry runs show the outcome:** each target's `status_on_create` tells the agent whether the post will wait for approval.
- **Audit log:** every create, approval, edit, publish and failure is recorded with whether a human or an agent did it, the key id, and the MCP client name. The kill switch (`POST /v1/workspace/pause`) stops every publish to Telegram and every other platform at once.

Read more in [the autonomy dial](https://turboyap.com/docs/autonomy-dial).

## FAQ

### What photo size does the Telegram Bot API accept?

Photos sent by URL can be up to 5 MB, with width plus height at most 10,000 pixels and an aspect ratio no more extreme than 20:1. Turboyap converts other formats to JPEG and resizes images to fit within 2560×2560 pixels.

### How long can a Telegram photo caption be?

1,024 characters, compared with 4,096 for a text message. For a longer message, keep the photo's caption short and send the rest as a [text message](https://turboyap.com/api/telegram/text-message).

### Can I add a link preview to a Telegram photo post?

No. Link previews apply to text messages only; on photo posts Turboyap ignores `link` with a warning. Put the URL in the caption instead.

## Related

- [Telegram text message API](https://turboyap.com/api/telegram/text-message) (beta)
- [Telegram album API](https://turboyap.com/api/telegram/album) (beta)
- [TikTok photo post API](https://turboyap.com/api/tiktok/photo-post)
- [Instagram image post API](https://turboyap.com/api/instagram/image-post)
- [Bluesky image post API](https://turboyap.com/api/bluesky/image-post) (beta)
- [Discord image message API](https://turboyap.com/api/discord/image-message) (beta)
- [Post one slideshow to TikTok and Instagram](https://turboyap.com/docs/slideshows)
- [REST API reference](https://turboyap.com/docs/api)
- [Platform rules](https://turboyap.com/docs/platform-rules)
- [Social media API limits tracker](https://turboyap.com/tools/social-api-limits)
