turboyap

TikTok slideshow API

Post TikTok slideshows (photo carousels) through an API or MCP server: the 35-image limit, formats, inbox vs direct mode, example requests, and every error with its fix.

Early access: publishing slideshows to TikTok is built and being tested in the REST API, @turboyap/sdk and the @turboyap/mcp server. It opens to early-access users as TikTok approves Turboyap; public posts need TikTok's audit. Meanwhile, test mode runs the same requests against a simulated TikTok account.

What a TikTok slideshow is

A TikTok slideshow is a photo post with several images that viewers swipe through, usually with a sound playing underneath. TikTok's Content Posting API calls it a photo post with media_type: PHOTO; Turboyap calls it type: "slideshow".

Turboyap uploads your slides once, converts and resizes them for TikTok, and either sends the slideshow to the creator's TikTok inbox to add a sound (the default) or posts it directly.

Limits

What TikTok accepts for this post type. Turboyap's planner checks every one of these before a post is scheduled (the same data as platform rules), and each value links to the TikTok documentation it comes from.

LimitValueSource
Images per post1–35TikTok: photo post reference
FormatsJPEG, WebP (Turboyap converts anything else to JPEG)TikTok: media transfer guide
File size20 MB per imageTikTok: media transfer guide
DimensionsFits 1080×1920 (Turboyap resizes to fit)TikTok: media transfer guide
Aspect ratioAnyTikTok: photo post reference
Caption4,000 characters (sent as description)TikTok: photo post reference
Title90 charactersTikTok: photo post reference
Rate limit6 post-init requests per minute per accountTikTok: photo post reference
Rate limit30 status checks per minute per accountTikTok: get post status

Compare with other platforms in the social media API limits tracker.

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 isn't public yet, and @turboyap/sdk and @turboyap/mcp aren't on npm yet. These examples show the API as built.

curl

# 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": "slideshow",
    "media_ids": ["med_…", "med_…", "med_…"],
    "caption": "Ice cream or frozen dairy dessert? Read the label.",
    "targets": [{ "account_id": "acct_tiktok…", "tiktok": { "mode": "inbox" } }],
    "schedule_at": "2026-10-20T18:00:00-04:00",
    "fit": { "overflow": "truncate" },
    "idempotency_key": "tiktok-slideshow-2026-10-20",
    "dry_run": true
  }'

TypeScript with @turboyap/sdk

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 === "tiktok")!;

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

const post = {
  type: "slideshow" as const,
  media_ids,
  caption: "Ice cream or frozen dairy dessert? Read the label.",
  targets: [{ account_id: account.id, tiktok: { mode: "inbox" as const } }],
  schedule_at: "2026-10-20T18:00:00-04:00",
  fit: { overflow: "truncate" as const },
  idempotency_key: "tiktok-slideshow-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:

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

Then ask:

Upload the slides in ~/content/icecream and schedule them as a TikTok slideshow for Tuesday 6pm ET. Use inbox mode so I can add a trending sound. Dry-run first and fix anything TikTok rejects.

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.

Inbox and direct mode

ModeWhat happensTarget status
inbox (default)Sends the post to the creator's TikTok inbox to finish in TikTok's editor (add a trending sound, then post). Works before TikTok's app audit.sent_to_inbox, then published once you post it in TikTok
directPosts straight to the account. Every direct post waits for a human to approve it and choose the privacy level, whatever the autonomy level. Until Turboyap passes TikTok's audit, direct posts are private (SELF_ONLY).publishing → processing → published

Direct mode needs a privacy_level the creator allows. auto_add_music only applies to direct posts; in inbox mode you pick the sound in the app.

A direct-mode target
{
  "account_id": "acct_tiktok…",
  "tiktok": {
    "mode": "direct",
    "privacy_level": "PUBLIC_TO_EVERYONE",
    "auto_add_music": true,
    "cover_index": 0,
    "title": "Read the label"
  }
}
TikTok options on each target (targets[i].tiktok)
OptionValuesNotes
mode"inbox" or "direct"Defaults to inbox.
titleUp to 90 charactersShown above the description.
privacy_levelPUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLYRequired for direct posts; must be one the creator allows.
auto_add_musictrue or falseDirect posts only.
cover_indexZero-based slide indexWhich slide is the cover.
disable_commenttrue or false
brand_content_toggle, brand_organic_toggletrue or falseBranded content disclosure.

Photos are pulled from Turboyap-hosted URLs on a domain verified with TikTok.

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.

CodeWhenFix
too_many_itemsMore than 35 images for TikTok.Set fit.overflow to "truncate" to post the first 35, or pass targets[i].media_ids with your best 35 slides (keep your call-to-action slide).
caption_too_longCaption over 4,000 characters.Shorten it, or pass targets[i].caption with a version under 4,000 characters.
title_too_longtiktok.title over 90 characters.Shorten the title to 90 characters.
invalid_requestDirect mode without tiktok.privacy_level.Set tiktok.privacy_level (e.g. "PUBLIC_TO_EVERYONE"), or use mode "inbox" to finish the post in TikTok.
invalid_requesttiktok.cover_index is past the last slide.Use a zero-based index of an existing slide.
media_format_unsupportedA 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_pastschedule_at is more than a minute in the past.Use "now" or a future time with a timezone offset.
account_unavailableThe TikTok account needs to be reconnected.Reconnect the account, then retry.
unsupported_platformA target is on a platform Turboyap doesn't support yet.Remove that target. Supported platforms: tiktok, instagram, bluesky (beta), telegram (beta), discord (beta).
insufficient_scopeThe 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_allowedThe 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_scopeAn 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_outcomeTurboyap 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 TikTok itself appear on the target's error with TikTok's own code. Turboyap retries rate limits and temporary failures with backoff, and marks the account for reconnection when TikTok rejects its token.

Approval and audit

Read more in the autonomy dial.

FAQ

How many photos can a TikTok slideshow have through the API?

Up to 35 images per post. Turboyap checks the count before scheduling and returns too_many_items with a fix if you send more.

Can I add music to a TikTok slideshow through the API?

Not a specific track. In inbox mode (Turboyap's default) you pick the sound in TikTok before posting. In direct mode, auto_add_music: true lets TikTok add a recommended sound.

Why did my TikTok post through the API come out private?

TikTok makes every post from an unaudited app private (SELF_ONLY). Until Turboyap passes TikTok's audit, direct posts are private. Inbox mode isn't affected: the post lands in your TikTok inbox and you publish it yourself in the app.

Which image formats does a TikTok slideshow accept?

JPEG and WebP, up to 20 MB each and 1080p. Turboyap converts other formats, such as PNG, to JPEG and resizes images to fit.