
Upscale Video with Python: Batch API Pipeline and Costs
Upscale video with Python at batch scale: an 80-line pipeline with rate-limit budgeting, cost estimates from $0.002/s, and crash-safe resume on a video API.
To upscale video with Python at any real volume, the problem is not the API call. One clip is a POST and a polling loop, twenty lines, done. The problem is the hundred-clip version: staying under the rate limit while polling, knowing the bill before you submit, and resuming a batch that died at clip 61 without paying for clips 1 through 60 again.
This guide builds that pipeline against reAPI's video enhancement endpoints, where Topaz Video Upscaler runs $0.044/s of source footage on the standard tier and enhance-video-1.0 starts at $0.002054/s[1][2]. Every number and contract detail below comes from the models' live pages in August 2026. If you want the tool comparison instead (open source, desktop, API), that is a separate guide: how to upscale video with AI.
TL;DR
- The API contract is batch-friendly by design: async submit returns a task id immediately, billing is per second of server-probed source footage, and failed tasks refund automatically, so a crashed batch never double-charges[1].
- The one hard constraint is 5 requests/second per user, polling included[1]. Four workers polling every 2.5 seconds uses about a third of that budget and leaves room for submits.
- Estimate before you submit: 100 one-minute clips cost $264 through Topaz standard, $24.64 through enhance-video-1.0 at standard 1080p[1][2]. The tier decision is worth ten times more than any code optimization.
- Persist task ids to disk as you submit. Resume then costs nothing: completed tasks are already paid and their output URLs still resolve.
- The full pipeline is about 80 lines, standard library plus
requests.
What the API contract gives a batch job
Three contract details shape the whole design, all from the model's own reference[1]:
Submit is asynchronous. POST /api/v1/videos/generations returns {"id", "status": "processing"} immediately; the render happens server-side and you poll GET /api/v1/tasks/<id> until status is completed or failed. A batch is therefore a set of open tasks you track, not a queue you block on.
Billing follows the source clip, not the output. The platform probes the source video's length server-side and bills per second of it: $0.044/s standard or $0.077/s max for Topaz upscaling, $0.002054/s to $0.016429/s for enhance-video's standard tier depending on target resolution[1][2]. That makes cost a pure function of footage duration, which is why the estimator below works.
Failures refund themselves. Credits are reserved at submit and refunded in full when a task fails[1]. The client never needs compensation logic; it only needs to record what failed and decide whether to resubmit.
Estimate the bill before you submit
Duration is the only input that matters. If the clips are local before upload, ffprobe reads it in one call; if they are already hosted, take durations from your own metadata.
import subprocess, json
def probe_seconds(path: str) -> float:
out = subprocess.run(
["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", path],
capture_output=True, text=True, check=True,
)
return float(json.loads(out.stdout)["format"]["duration"])
RATES = { # $ per second of source, August 2026
"topaz-standard": 0.044,
"topaz-max": 0.077,
"enhance-720p": 0.002054,
"enhance-1080p": 0.004107,
"enhance-4k": 0.016429,
}
def estimate(paths, rate_key):
total = sum(probe_seconds(p) for p in paths)
return total, total * RATES[rate_key]What that arithmetic looks like for 100 one-minute clips:
| Tier | Rate | 100 × 60 s |
|---|---|---|
| enhance-video-1.0, standard 720p | $0.002054/s | $12.32[2] |
| enhance-video-1.0, standard 1080p | $0.004107/s | $24.64[2] |
| enhance-video-1.0, standard 4K | $0.016429/s | $98.57[2] |
| Topaz Video Upscaler, standard | $0.044/s | $264.00[1] |
| Topaz Video Upscaler, max | $0.077/s | $462.00[1] |
The 37× spread between the cheapest and most expensive row is the real optimization surface. No amount of Python tightens a bill the way choosing the right tier does; the selection logic is simple enough to encode as a rule (clean source going up in resolution: enhance tier; degraded source needing detail recovery: Topaz).
Upscale video with Python: an 80-line batch pipeline
The design: submit everything up front (submits are cheap and fast), persist the task map to disk immediately, then poll with a small worker pool. State lives in one JSON file keyed by source URL.
import json, pathlib, time
from concurrent.futures import ThreadPoolExecutor
import requests
API = "https://reapi.ai/api/v1"
HEADERS = {"Authorization": "Bearer rk_live_..."}
STATE = pathlib.Path("batch_state.json")
POLL_INTERVAL = 2.5 # 4 workers / 2.5s ≈ 1.6 req/s, well under the 5/s cap
WORKERS = 4
def load_state():
return json.loads(STATE.read_text()) if STATE.exists() else {}
def save_state(state):
STATE.write_text(json.dumps(state, indent=2))
def submit(video_url, state):
if video_url in state: # already submitted on a previous run
return
r = requests.post(f"{API}/videos/generations", headers=HEADERS, json={
"model": "topaz-video-upscaler",
"video_url": video_url,
"upscale_factor": "2",
}, timeout=30)
r.raise_for_status()
state[video_url] = {"task_id": r.json()["id"], "status": "processing"}
save_state(state) # persist before moving on
def poll_one(video_url, entry):
while True:
r = requests.get(f"{API}/tasks/{entry['task_id']}", headers=HEADERS)
body = r.json()
if body["status"] in ("completed", "failed"):
return video_url, body
time.sleep(POLL_INTERVAL)
def run(video_urls):
state = load_state()
for url in video_urls:
submit(url, state)
time.sleep(0.25) # submits: 4/s, inside the budget
open_items = [
(u, e) for u, e in state.items() if e["status"] == "processing"
]
with ThreadPoolExecutor(max_workers=WORKERS) as pool:
for url, body in pool.map(lambda p: poll_one(*p), open_items):
state[url]["status"] = body["status"]
if body["status"] == "completed":
state[url]["output"] = body["output"]
else:
state[url]["error"] = body.get("error")
save_state(state)
done = sum(1 for e in state.values() if e["status"] == "completed")
failed = [u for u, e in state.items() if e["status"] == "failed"]
print(f"{done} completed, {len(failed)} failed")
for u in failed:
print("FAILED:", u, state[u].get("error"))
run([
"https://your-cdn.com/clip-001.mp4",
"https://your-cdn.com/clip-002.mp4",
])Swap the payload for enhance-video-1.0 (tool_version, scene, resolution instead of upscale_factor) and nothing else changes[2]. Source URLs must be public HTTPS; base64 uploads are rejected platform-wide.
The rate-limit budget, explicitly
The platform caps each user at 5 requests/second, and polling counts[1]. The pipeline's spend:
- Submits: throttled to 4/s during the submit phase, which is short.
- Polling: 4 workers × one request per 2.5 s = 1.6 req/s steady state.
- Headroom: ~3.4 req/s left for a second script, a dashboard, or manual curl checks.
Raising WORKERS to 12 with a 2.5 s interval would push polling alone to 4.8 req/s and start returning 429s the moment anything else touches the API. More workers do not finish renders faster anyway; the render time is server-side. Workers only bound how quickly you notice completion, and noticing 2.5 seconds late costs nothing.
Handling failures without double-paying
The platform's failure contract does the heavy lifting: a failed task refunds its reserved credits in full, automatically[1]. The pipeline's job reduces to bookkeeping:
- Record the failure, including the
errorobject with itscode,message, andrequest_id(the error-code reference lives in the API docs[3]). - Do not auto-resubmit blindly. A clip that failed because the source URL 404s will fail again at the same cost of zero, but a wall of retries burns your rate budget. Fix the input, then rerun the script; the state file skips everything already completed.
- Trust resume. Completed entries keep their output URLs, which point at CDN-rehosted files that do not expire[1], so a batch interrupted at clip 61 restarts with 60 paid results intact and only the remainder outstanding.
FAQ
How do I upscale video with Python for free?
Not through a hosted API; rendering costs someone GPU time. The free route is running open-source upscalers like video2x or Real-ESRGAN on your own GPU, which trades money for hardware and setup, covered in our tool comparison. The API route starts at $0.002054 per source second[2], and signup credits cover the first test calls.
How many clips can I process in parallel?
Submit as many as you like; the constraint is request rate, not open tasks. Keep total request throughput, submits plus polling, under 5 per second[1]. Four polling workers at a 2.5-second interval is a comfortable steady state.
What does it cost to upscale 100 videos?
Duration decides. At one minute per clip: from $12.32 (enhance-video, 720p) to $462 (Topaz max)[1][2]. Run the estimator on real durations before submitting; it is four lines of ffprobe.
Can I resume a batch after my script crashes?
Yes, if task ids were persisted at submit time. Completed tasks stay completed and paid, their output URLs remain valid, and failed tasks were already refunded[1]. The state-file pattern above makes resume the default behavior rather than a recovery feature.
Should I upscale to 4K in a batch job?
Only when the destination screen demands it. The 4K tier costs 8× the 720p tier on enhance-video[2], and social feeds re-compress uploads anyway. A common pattern is batching everything at 1080p and re-running the handful of hero clips at 4K.
How do I know the source duration the platform will bill?
The platform probes the hosted file server-side and bills its real length[1]. Local ffprobe on the same file gives the same number; discrepancies mean the hosted copy differs from the local one, which is worth catching before submitting a hundred of them.
Running the first batch
Start with three clips, not a hundred: one clean, one degraded, one long. That run validates the state file, shows real per-clip costs against the estimate, and surfaces input problems while they cost cents. Then point the script at the full list and let the refund contract and the state file absorb whatever goes wrong. The whole point of doing this in Python is that to upscale video with Python a second time, the command is just rerunning the script, and the second run only pays for what the first one did not finish.
References
- reAPI. Topaz Video Upscaler — model page: live pricing, task lifecycle, rate limits. Retrieved August 2026 from reapi.ai/models/topaz-video-upscaler
- reAPI. Enhance Video 1.0 — model page: live per-second tier pricing. Retrieved August 2026 from reapi.ai/models/enhance-video-1-0
- reAPI. API error codes reference. Retrieved August 2026 from reapi.ai/docs/api/errors
Further reading
- reAPI. Upscale video with AI: open source, desktop, or API. reapi.ai/blog/upscale-video-with-ai
- reAPI. Topaz Video Upscaler API docs. reapi.ai/docs/topaz-video-upscaler
Author

Categories
More Posts

DeepSeek V4 Flash Official Release: 0731 Agent and Codex Upgrades
DeepSeek V4 Flash 0731 is live in API public beta with stronger agent benchmarks, native Responses API support, Codex integration, and the same model ID today.


MiniMax H3 vs Seedance 2.5: Which AI Video Model Wins?
Compare MiniMax H3 vs Seedance 2.5 on duration, 2K output, native audio, multimodal references, editing, pricing, API access, and best use cases.


DeepSeek V4 1M Context: max_tokens, Billing, and Concurrency
DeepSeek V4 shares its 1M context between input and output, with a 384K maximum output. Learn max_tokens, cache billing, and concurrency limits.
