Sume job failed artifact_too_large: shrink the output, don't rerun
A Sume job failing with artifact_too_large or artifact_upload_rejected made its file but could not store it. Make the file smaller; rerunning changes nothing.

When a Sume job fails with public_reason artifact_too_large or artifact_upload_rejected, generation itself ran: the finished file was refused by artifact storage. It is not a timeout and not a capacity problem. The envelope is category: generation_rejected, stage: generation_processing, retryable: false, next_action: fix_input, so the fix is a smaller output, such as a shorter cut or lower resolution, not a rerun.
How are the two reasons chosen?
The job mapper reads the storage response status recorded on the job as artifact_upload_status. A 413 becomes artifact_too_large. Any other 4xx becomes artifact_upload_rejected, except 408 and 429, which are the transient statuses and stay out of this rule. Anything outside the 4xx range is not mapped here.
The messages differ accordingly. For 413: the generated file was larger than the artifact upload limit, so produce a smaller file, with a shorter cut, a lower resolution or a lower bitrate. For the other statuses: artifact storage rejected the generated file. Both end by saying that re-running this request unchanged will fail the same way. The public docs do not publish a numeric limit, so do not hard-code one.
What should I change first?
Change whichever input drives file size. For a Timeline render that means fewer or shorter segments, or a lower output resolution; for model output it means a shorter duration or lower resolution where the model's schema offers one. Then submit as a new request with a new Idempotency-Key; the same key replays the stored failure instead of trying again.
| Signal | public_reason | Retry the same request? |
|---|---|---|
| Storage status 413 | artifact_too_large | No |
| Storage status 4xx except 408, 429 | artifact_upload_rejected | No |
| Storage status 408 or 429 | not mapped here | Follow the job's retryable |
| Generation timeout | see job stage and retryable | Follow the job's retryable |
How do I tell it apart in code?
Branch on public_reason, then on the flag. This keeps a retry loop from burning attempts on a file that will be refused again.
def next_step(job: dict) -> str:
err = job.get("error") or {}
reason = err.get("public_reason")
if reason in ("artifact_too_large", "artifact_upload_rejected"):
return "shrink output (shorter, lower resolution or bitrate); new Idempotency-Key"
if err.get("retryable"):
return f"retry after {err.get('retry_after_seconds') or 30}s with the same key"
return f"do not retry; next_action={err.get('next_action', 'unknown')}"
print(next_step({"error": {"public_reason": "artifact_too_large", "retryable": False}}))Sources
Related posts
More in Developers
- pending_usd_micros vs held: what Sume's settle sweeper still owns
Sume /v1/usage splits open holds into held and pending_usd_micros. Read the two fields and settle_state to tell parked rows from spend, and when final flips.
- unpriced_usd_micros: legacy rows still inside Sume's debited total
unpriced_usd_micros in Sume /v1/usage is captured spend from legacy rows with no price-book stamp. It is inside debited, so never subtract or add it twice.
- TanStack Start webhook route for Sume jobs: verify the raw body
Receive a signed Sume job webhook in a TanStack Start server route: read request.text(), verify with the SDK, answer 204, and dedupe on job_id.
- Usage hold_counts: open, browser_session, billing_pending
A scoped Sume usage summary can show final false with money held. hold_counts says whether a turn runs, a Browser session is open, or work is being priced.
Written by Sume