20 TopView ads per ad group: 20 hook swaps from one body for $2.00

TikTok's TopView page allows at most 20 ads per ad group. Build 20 hook variants over one body with Timeline 1.0: 20 one-minute reservations, $2.00.

5 min readSume
All posts

Twenty hook swaps over one shared body fill a TopView ad group exactly. TikTok's TopView page sets the maximum at 20 ads per ad group (read 2026-10-07). In Sume, each 15-second Timeline render reserves one output minute at $0.10, so 20 variants cost $2.00 of assembly, before you pay for the hook and body clips themselves.

The discipline is to change one thing per variant, so a result tells you which hook did the work.

Constraints from the TopView page

From TikTok's TopView page. The review step and the freeze after preloading mean variants must exist before submission.

TopView limits that shape a variant batch (read 2026-10-07)
ConstraintValue
Ads per ad groupmaximum 20
Duration5-60 s (9-15 s recommended)
Pre-approvalfrom your sales representative before submission
Assets after preloading beginscannot be modified

Build 20 bodies in a loop

Each variant is a 5-second hook followed by a shared 10-second body, over a 15-second voice spine. The script builds the 20 bodies and totals the reserved minutes: ceil(audio.duration_seconds / 60) per render. Send each body to POST /v1/timeline-1.0/plan first, which is unbilled, then to render with a unique idempotency key.

import json
import math

body_url = "https://media.sume.com/artifacts/artf_demo/body.mp4"
vo_url = "https://media.sume.com/artifacts/artf_demo/vo.wav"
bodies = []
for n in range(1, 21):
    hook = "https://media.sume.com/artifacts/artf_demo/hook-%02d.mp4" % n
    bodies.append({
        "audio": {"url": vo_url, "duration_seconds": 15},
        "video": [
            {"source_url": hook, "start": 0, "duration": 5},
            {"source_url": body_url, "start": 5, "duration": 10},
        ],
    })
minutes = sum(math.ceil(b["audio"]["duration_seconds"] / 60) for b in bodies)
print(len(bodies), "variants,", minutes, "minutes, $%.2f" % (minutes * 0.10))
print(json.dumps(bodies[0]["video"][0]))

Limits of the shared voice

One spine over 20 hooks means the hook's own audio is replaced, so a hook that depends on speech needs its own spine. For that, give the variant its own audio.url; the cost rule is unchanged.

Each render still needs a probe: duration within 5-60 s, 1080x1920 against TikTok's 540x960 px minimum, and a bitrate of at least 2,500 kbps (both listed for non-Spark ads, read 2026-10-07).

What it does not do

Sume does not run the ad group, tell you which hook won, or lift the 20-ad cap. Twenty is a ceiling set by TikTok; you may wish to spend fewer, bigger bets. Collect the files per Jobs and results.

How to read the results

Twenty variants are only useful if each differs by one variable. Name the files by hook, keep body, voice, and music constant, and write the hypothesis next to each key in the job record before the ad runs.

If you cannot afford twenty hook clips, five hooks across four voice reads is also twenty, with a different lesson. Either way the assembly cost stays at $0.10 per render, and the creative work is the real budget.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume