402 halfway through 50 clips of 30-second video: stop and resume

When balance runs out mid-batch Sume answers 402 insufficient_credits before provider work starts. Halt, keep the paid job ids, and resume the rest later.

5 min readSume
All posts

When a 50-clip batch of 30-second videos hits 402 insufficient_credits, stop submitting, keep every job id you already have, and resume only the clips that were never accepted. A 402 means Sume could not reserve the estimated cost, and it is answered before provider work starts, so the rejected clip costs nothing.

Thirty seconds makes each clip expensive enough that this is a real scenario. At Sume's Wan 3.0 rates a 30-second 720p clip is $3.75, and a Seedance 2.5 clip at 1080p is $42.65.

Why it fails partway

At submit Sume reserves the estimated amount from the workspace balance, and a successful completion captures it. Each accepted clip therefore commits money immediately, even while it sits in queued. With $150.00 and $3.75 clips, 40 are accepted (40 x $3.75 = $150.00), and the 41st returns 402.

Do not retry a 402 in a loop. It is not a rate limit. Add funds or choose a cheaper request, such as 480p at $1.875 per 30-second Wan 3.0 clip, then continue.

Halt on 402, back off on queue_full

Three different errors can stop a batch, and they need different reactions. 402 is a balance stop. 429 queue_full means the workspace has no accepted-job capacity left, so wait for jobs to finish and retry with the same key. 429 rate_limited means slow down and use retry-after.

import os, time, requests
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
URL = "https://api.sume.com/v1/videos"

def submit(i, prompt):
    body = {"model": "wan-3.0", "prompt": prompt, "duration": 30,
            "resolution": "720p", "aspect_ratio": "16:9"}
    h = {**H, "Idempotency-Key": f"batch-7-clip-{i:03d}"}
    while True:
        r = requests.post(URL, json=body, headers=h, timeout=30)
        code = r.json().get("error", {}).get("code") if r.status_code >= 400 else None
        if code in ("queue_full", "rate_limited"):
            time.sleep(float(r.headers.get("retry-after", 30)))
            continue
        return r

jobs, rest = {}, []
for i, p in enumerate(open("prompts.txt").read().splitlines()):
    if rest:
        rest.append((i, p)); continue
    r = submit(i, p)
    if r.status_code == 402:
        rest.append((i, p)); continue
    jobs[i] = r.json()["id"]
print("accepted", len(jobs), "unsent", len(rest))

What to save

Write the jobs mapping to disk before you exit. Accepted clips keep running and bill normally, so you must still collect them. The unsent list is your resume point: top up, then run the same script again. The stable Idempotency-Key per clip means a replay of an accepted clip returns the original job instead of a second charge.

What each stop signal means in a 50-clip batch, Sume docs read 2026-10-05
SignalMeaningReaction
402 insufficient_creditsCannot reserve the estimateHalt, add funds or cut cost
429 queue_fullNo accepted-job capacity leftWait, retry with the same key
429 rate_limitedRequest budget exceededWait for retry-after
409 idempotency_conflictSame key, different payloadUse a new key for a new payload

Cost of the whole batch up front

Fifty 30-second Wan 3.0 clips cost $93.75 at 480p, $187.50 at 720p, and $375.00 at 1080p. Check GET /v1/balance against that number before you start, and consider a draft pass at 480p first. A 402 should be the safety net, not the plan.

Before you resume

Top up first, then resume. A 402 insufficient_credits comes before any provider work, so no clip was half-rendered by the failing submit, but the clips already accepted continue and are already reserved.

When you resume, skip every clip that has a job id and submit only the rest. Add the same idempotency key per clip you used before, so a clip whose first response you lost does not become two renders.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume