Python and SQLite: log every video job's usage.cost by model

A 13-line stdlib script stores each completed job's usage.cost in SQLite, ignores duplicates by job id and prints spend per model. Seedance 2.5 5 s is $2.89.

4 min readSume
All posts

Video spend is the line that surprises teams, because a 1080p Seedance 2.5 clip costs 2.46 times the 720p one and nobody remembers which job used which. The poll response from GET /v1/videos/{id} has the answer: once the job is completed it carries usage.cost, the Sume billable amount in USD. This post stores that number in SQLite so you can ask what each model cost you this week.

The script is 13 lines of Python standard library. It takes a saved poll body as an argument, so you can run it from a worker, a webhook handler or by hand.

The script

Run it with python ledger.py job.json. job_id is the primary key and the insert is INSERT OR IGNORE, so replaying the same completed job, which webhook retries and double polling both do, does not count it twice. It was run twice against the same file and both runs printed one row.

import json, sqlite3, sys

# usage: python ledger.py job.json  (a completed GET /v1/videos/{id} body)
job = json.load(open(sys.argv[1]))
db = sqlite3.connect("video_spend.db")
db.execute("""CREATE TABLE IF NOT EXISTS spend (
    job_id TEXT PRIMARY KEY, model TEXT, cost_usd REAL, logged_at TEXT DEFAULT CURRENT_TIMESTAMP)""")
if job["status"] == "completed":
    db.execute("INSERT OR IGNORE INTO spend (job_id, model, cost_usd) VALUES (?, ?, ?)",
               (job["id"], job["model"], job["usage"]["cost"]))
    db.commit()
for row in db.execute("SELECT model, COUNT(*), ROUND(SUM(cost_usd), 4) FROM spend GROUP BY model"):
    print(row)

Why only completed jobs

Sume reserves the price at submit and settles when the job ends. A failed or cancelled job is not a spend line, so the script skips anything that is not completed. The reserve and the final cost can differ for jobs where the real length is known only at the end, which is why you log usage.cost from the finished job, not the estimate you made before submitting. If you also record the model id and the requested duration next to the cost, you can later divide by seconds and spot a job that cost more than its model's list rate suggests, for example a clip that carried reference videos.

What the numbers should look like

If a row looks wrong, compare it with the list price times 1.25 that Sume charges on every model. These are the clip prices from the pricing tables for one 5-second clip.

Expected usage.cost for one 5-second clip on Sume (pricing tables, read 2026-10-05)
Model and settingsusage.cost
kling-3, 720p, audio off$0.70
kling-3, audio on$1.05
wan-3.0, 480p$0.31
wan-3.0, 1080p$1.25
seedance-2.5, 720p, 9:16$2.89
seedance-2.5, 1080p, 9:16$7.11

Extending it

Add a prompt_tag column and fill it from your own request record to answer which campaign spent the money. Keep the table in a file you back up, because the cost on the job is the only place the billable amount for a single clip appears. For a monthly total, group by strftime('%Y-%m', logged_at) instead of by model.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume