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
- TikTok PULL_FROM_URL times out after one hour: keep the URL live
TikTok pulls a PULL_FROM_URL file at up to 100 Mbps and times out one hour after it starts. The URL must stay live; a Sume durable URL has no expiry.
- Concatenate audio files by API: parts, and no top-level url
Sume Timeline audio concat needs a parts array and refuses a top-level url or ranges. Each operation accepts its own keys; mixing them returns a stable code.
- Split audio into segments by timestamps: url and ranges
Sume Timeline audio split needs a top-level url and 1 to 20 ranges. A range with end at or before start is refused; each segment gets its own audio_url.
- Image over video longer than the clip: compose's 300 s ceiling
Sume Timeline compose length comes from the video layer, capped at 300 seconds. Asking for more than the file clamps with a warning and the job still succeeds.
Written by Sume