An AI series render ledger: job id, key, plan minutes, warnings

TikTok is testing spam detection on AI-content accounts. Keep a CSV of every Sume render: job id, idempotency key, plan minutes, status, file and warnings.

6 min readSume
All posts

Why a ledger, and what it is not

The October platform roundup reports that TikTok is testing improved detection for accounts dedicated to AI-generated spam, with attention on political, financial and medical content. If you run a branded series that is made with AI, you want to be able to say, for any episode, what was generated, when, and from which request. A ledger of your own render jobs answers that. It is a record you keep, not a credential TikTok accepts or a defense against any review, and we do not claim it changes how a platform treats an account.

What Sume gives you for this is thin and useful: every job has an id, every write can carry an Idempotency-Key you choose, the plan returns billable_minutes before you pay, and a finished job returns a result with its warnings. The ledger stitches those together in a CSV you control.

What to record and where each field comes from

Each column maps to a documented field. Nothing in it is invented by the script.

Ledger columns and their source in the Sume docs (read 2026-10-06)
ColumnSourceWhy you want it
episodeYour own numberingJoin key for publishing
idempotency keyThe header you sentReplays the same job, never a duplicate
job idrequest_id in the submit responseLook the job up again at any time
billable minutestimeline plan responseThe cost you expected before rendering
statussume_status on GET /v1/jobs/:id/statuscompleted, failed or canceled
video urlvideo_url in the job resultThe finished file on media.sume.com
warningswarnings[] in the job resultPadded, looped, resampled or clamped

The script

submit plans first, then renders with a stable key built from season, episode and a version suffix, and returns the first four columns. settle reads the status and, when a result is ready, the file and warnings. write dumps rows to a CSV. Call submit per episode and settle later, since a render is a job and not a response.

import csv, json, os, urllib.request

def api(method, path, body=None, key=None):
    h = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
         "Content-Type": "application/json", "User-Agent": "sume-example/1.0"}
    if key:
        h["Idempotency-Key"] = key
    data = json.dumps(body).encode() if body else None
    with urllib.request.urlopen(urllib.request.Request(
            "https://api.sume.com" + path, data, h, method=method)) as r:
        return json.load(r)

def submit(ep, body):
    key = f"s1-ep{ep:02d}-v1"
    plan = api("POST", "/v1/timeline-1.0/plan", body)
    job = api("POST", "/v1/timeline-1.0/render", body, key)
    return [ep, key, job["request_id"], plan["billable_minutes"]]

def settle(row):
    st = api("GET", f"/v1/jobs/{row[2]}/status")
    out = {}
    if st.get("result_ready"):
        res = api("GET", f"/v1/jobs/{row[2]}/result")
        out = res.get("result", res)
    return row + [st.get("sume_status"), out.get("video_url", ""),
                  json.dumps(out.get("warnings", []))]

def write(rows, path="ledger.csv"):
    with open(path, "w", newline="") as f:
        csv.writer(f).writerows(rows)

Keys and versions

Build keys from facts you control: season, episode, and a version you bump when you change the body on purpose. The point of the key is that a retry after a timeout reuses the same request instead of starting a second one; check the idempotency docs for the exact replay and conflict behavior before you rely on it. When you intentionally re-render episode 7 with a new mix, bump the version to v2 and the ledger keeps both rows, with the old file untouched.

Do not put secrets or personal data in keys or in the ledger. Keep it to ids, numbers, URLs and codes.

What the ledger does not do: it does not prove how a clip was made beyond the Sume request, it does not replace the disclosure or labeling controls a platform gives you in its own upload flow, and it does not decide whether a series is spam. The platforms decide that on their own signals. Treat the ledger as an internal control with two jobs: reconcile cost against your invoice, and let a person answer 'which request produced this file' in under a minute.

A practical extra column is the publish URL on each platform, filled in after upload. With that one column you can walk from a live Short back to the exact job id, key and body version, and from there to the plan you approved. Keep the Timeline body itself in version control next to the CSV, named by the same key, so the record is complete: the inputs, the request and the result.

If you run more than one series, give each its own key prefix and its own CSV. Mixing them makes reconciliation harder, and a prefix is free.

Using it

Run settle on every row before you publish an episode and refuse to publish rows whose status is not completed. Read the warnings column too, because a padded or looped short source is not a failure and will not stop the job; it will only show up here. If a platform later asks how an episode was made, you have a dated record of the request and the file. If it asks nothing, you have a cost log that matches your invoice line for line. Confirm live rates in GET /v1/catalog, and keep the documented price per whole output minute in mind when you add a cost column.

This is a small habit, and the whole script is under 30 lines. It is the cheapest piece of governance a generated series can have.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume