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.

4 min readSume
All posts

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.

Photo post init fields, from the TikTok reference read 2026-10-01.
FieldRule
media_typeOnly PHOTO
source_info.sourceOnly PULL_FROM_URL
photo_imagesUp to 35 public URLs
photo_cover_indexRequired, counted from 0
RateSix 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

All Developers posts

Written by Sume