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.

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.
| Model and settings | usage.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
- Python receiver for a Sume /v1/videos callback_url, signature checked
A standard-library Python webhook receiver for Sume video jobs: checks x-sume-webhook-signature, refuses an empty secret, rejects stale timestamps.
- Python Sume webhook handler that accepts the webhook.test event
Verify the sume-v1 signature over timestamp.body, refuse an empty secret, and accept webhook.test, which has no job_id. Stdlib Python, runs offline.
- Python: three Wan 3.0 hooks from one reference image, with costs
A Python script for Sume's /v1/videos: submit three Wan 3.0 hook prompts with one reference image at 480p, poll each job, and print the usage cost.
- Python TTS cost calculator: MAI-Voice-2.1, Flash and Sume per job
A short Python function prices any script on MAI-Voice-2.1 ($22/M), Flash ($15/M) and Sume (list x 1.25, rounded up per job); 210 vs 211 characters shown.
Written by Sume