Python cost cap for a mixed media job: round up, then refuse
A 25-line Python estimator with Decimal prices that rounds the total up to the cent, as Sume's reservation does, and exits before submit if it is over your cap.

Sum the list prices of every call in a job with Decimal, round the total up to the cent, and refuse to submit if it passes your cap. For one 8-second Wan 3.0 clip at 720p, three 2K Nano Banana 2.1 stills, a 900-character voiceover and a one-minute render, the exact total is 1.59275 dollars, which rounds up to $1.60. That matches how Sume's catalog describes reservation: the estimate is stored in USD micros and rounded up to cents for balance compatibility.
The estimator
The price table holds four lines from the Sume catalog, read 2026-10-09. Keep the table in one place and regenerate it from GET /v1/catalog rather than copying it into many scripts. The script runs as is.
from decimal import Decimal as D, ROUND_CEILING
PRICE = { # USD, Sume list prices read from /v1/catalog
"wan-3.0:720p": D("0.125"), # per output second
"nano-banana-2.1:2k": D("0.15"), # per image
"tts": D("0.0475"), # per 1,000 characters
"render": D("0.10"), # per ceil(output minute)
}
def estimate(plan, cap):
total = D(0)
for kind, qty in plan:
total += PRICE[kind] * qty
total = total.quantize(D("0.01"), rounding=ROUND_CEILING)
if total > cap:
raise SystemExit(f"refuse: {total} is over the {cap} cap")
return total
plan = [("wan-3.0:720p", 8), ("nano-banana-2.1:2k", 3),
("tts", D(900) / 1000), ("render", 1)]
print(estimate(plan, D("2.00")))The arithmetic it performs
Each line multiplies a unit price by a quantity. The voiceover quantity is 900 / 1000 = 0.9 thousand characters.
| Line | Math | Cost |
|---|---|---|
| Wan 3.0 720p | 8 x 0.125 | $1.00 |
| Nano Banana 2.1 2K | 3 x 0.15 | $0.45 |
| TTS | 0.9 x 0.0475 | $0.04275 |
| Timeline render | 1 x 0.10 | $0.10 |
| Exact total | $1.59275 | |
| Rounded up to the cent | $1.60 |
Per-job and per-day caps
Use two caps. A per-job cap catches a bug in one request, such as a duration of 120 instead of 12. A daily cap catches a loop. With the $1.60 job above, a $25.00 daily cap allows 15 jobs (25 / 1.60 = 15.6, rounded down).
Add the daily total to a small file or database keyed by date, and compare the sum of today's estimates plus the new one with the cap. Do it before the submit call, never after.
Test the estimator with a case you can check by hand: one render minute alone must return 0.10, and ten thousand characters of TTS must return 0.48 (10 x 0.0475 = 0.475, rounded up). If a Sume price changes, those tests fail and tell you to update the table.
Why refuse on the client
The server will also refuse. Sume returns 402 insufficient_credits when it cannot reserve the estimate from the balance, and 429 queue_full when the workspace is out of accepted-job capacity. A local cap catches a different mistake: a loop that is allowed by the balance but not by your budget. With a $2.00 cap the script above passes; change the clip to 12 seconds and the total is $2.10 (12 x 0.125 = 1.50, plus 0.45, 0.04275, 0.10 = 2.09275, rounded up), and it exits with a refusal.
The script does not submit anything. Put the call to your submit function after estimate() returns, and pass an Idempotency-Key so a retry is safe.
Extending the table
Adding a line is a one-row change. Keep the keys in a model:size form, and quantities in the unit of the price.
- Per-second video: quantity is output seconds.
- Per-image: quantity is image count.
- TTS: quantity is characters divided by 1,000.
- Timeline render: quantity is the billed minutes,
ceil(seconds / 60)with a minimum of 1.
Sources
Related posts
More in Developers
- Python httpx 429 handler for Sume: retry-after, then ratelimit-reset
A small async Python wrapper for the Sume API that waits on retry-after, falls back to ratelimit-reset, and never retries a POST that lacks an Idempotency-Key.
- Python verifier for Sume webhooks: rotation header and empty secrets
A Python function that checks the sume-v1 HMAC over timestamp.raw_body, accepts either signature during rotation, and refuses an empty secret. Under 30 lines.
- Quantized H3 vs lossless H3 vs a hosted clip: compare fairly
MiniMax's guide says not to mix ComfyUI quantized and SGLang lossless outputs. How to set up a fair comparison with a hosted clip, including the seed catch.
- Re-base caption words after a trim: subtract actual_start_seconds
Caption words must use the trimmed file's clock. A word at 12.3-12.7 s becomes 1.9-2.3 s when the trim really started at 10.4, not 0.3-0.7 s.
Written by Sume