TikTok photo post API: 35 image URLs, PULL_FROM_URL, cover index
TikTok's photo post endpoint takes up to 35 public image URLs by PULL_FROM_URL and a photo_cover_index. How to plan the generated-image batches that feed it.

A TikTok photo post is one POST to /v2/post/publish/content/init/ with media_type set to PHOTO, up to 35 public image URLs in photo_images, source PULL_FROM_URL (the only allowed value) and a required photo_cover_index. Sume image jobs return public HTTPS URLs and up to four images each, so 35 slides take at least nine jobs.
Request fields are from TikTok's photo reference ("Last updated August 24, 2026", read 2026-10-01); Sume's side is from the image docs.
What does the init request need?
The endpoint needs a user access token with scope video.publish or video.upload, and each token is limited to six requests per minute. post_mode is DIRECT_POST or MEDIA_UPLOAD. The photo URLs must be publicly accessible and verified by your app; TikTok points to its URL-pulling guide for that step.
| Field | Rule |
|---|---|
media_type | Only PHOTO |
source_info.source | Only PULL_FROM_URL |
photo_images | Up to 35 public URLs |
photo_cover_index | Required, counted from 0 |
| Rate | Six requests per minute per access token |
How do generated images map to it?
Each finished Sume image job lists its images as media.sume.com URLs in data[], which is the shape photo_images wants. num_images is an integer from 1 to 4, so batch the slide prompts and collect the URLs in order. output_format is chosen per job (png, jpeg, jpg or webp). For a consistent character across slides, pass 1 to 10 reference URLs in image_urls on an edit-style job.
// Build the TikTok body from finished Sume image URLs.
function photoPostBody(urls, coverIndex, title) {
if (urls.length < 1 || urls.length > 35) throw new Error("1-35 images");
if (coverIndex < 0 || coverIndex >= urls.length) throw new Error("bad cover");
return {
media_type: "PHOTO",
post_mode: "MEDIA_UPLOAD",
post_info: { title },
source_info: {
source: "PULL_FROM_URL",
photo_cover_index: coverIndex,
photo_images: urls,
},
};
}
console.log(photoPostBody(["https://media.sume.com/img/a.png"], 0, "demo"));How do I stay under the rate limit?
The six-per-minute limit is on TikTok requests per user token, not on your Sume jobs. Generate all slides first, then send one init call per post. Slide count and cover choice are yours: decide the cover index after you have looked at the finished images, not before.
Where do the title and label fields go?
Those live in post_info and are covered separately: see the 90-rune title and 4000-rune description limits and the `is_aigc` label.
Sources
Related posts
More in Developers
- Fade out music at the end of a video: two fades, two caps
In Timeline 1.0, soundtrack.fade_out_seconds fades only the music bed (max 10 s). output.fade_out_seconds fades the whole render (0 to 5 s). Which to use.
- Crossfade longer than 1 second: the Timeline transition limit
Timeline 1.0 transitions are capped at 1 second and 50 percent of the shorter neighbouring clip. Longer ones return transition_too_long. What to do instead.
- Video starts on a black frame: fix the first Timeline segment
A render that opens on black usually has a fade or a late first clip. Timeline 1.0 refuses a first start other than 0 and any first-segment transition.
- WhatsApp audio message format: AAC, MP3, OGG Opus mono, 16 MB
WhatsApp accepts AAC, AMR, MP3, M4A and OGG (Opus, mono) up to 16 MB. Sume's audio detach returns mp3 or wav, so mp3 fits; ogg needs another tool.
Written by Sume