Format run completed but output is null: handle outcome degraded
A Sume Format webhook says completed, yet output is null. Outcome degraded means real media is in artifacts and your schema did not match. How to branch.

Branch on outcome, not on status. A Sume Format webhook can say status: OK and still carry outcome: degraded. That means the run completed and was billed, real media sits in artifacts[], but output is null because the projection did not match your output_schema. output_error gives the reason.
The four outcomes
| outcome | Meaning | Do this |
|---|---|---|
ok | Completed with output | Use output |
degraded | Completed and billed; media in artifacts[]; output is null | Read output_error, then use primary_output_url or artifacts[] |
error | The run did not complete | Read error.code |
| (canceled, skipped) | No webhook is sent | Handle on the call that caused it |
Why a good video can have a null output
Sume builds output from what the run made and said. If you bind a strict schema, the result must fit it. When the video exists but the object does not fit, the run keeps the media and drops the object rather than discarding paid work. The billed amount in usage is still real.
A handler that does not lose the video
Route on event (always format.run.terminal), dedupe on request_id, then branch on outcome. The payload under payload is byte-identical to data from GET /v1/format-runs/{run_id}, so one handler serves a webhook and a poll.
def handle(event):
p = event["payload"]
if event["outcome"] == "ok":
return p["output"]
if event["outcome"] == "degraded":
urls = [a["url"] for a in p["artifacts"]]
print("schema miss:", p.get("output_error"))
return {"fallback_urls": urls}
raise RuntimeError(event["error"]["code"])
print(handle({"outcome": "degraded", "payload": {
"artifacts": [{"url": "https://media.sume.com/artifacts/artf_x/full_video.mp4"}],
"output_error": "schema mismatch"}}))What a retry should and should not do
Do not re-run a degraded item just to get a clean output. The video is already made and billed, so a second run is a second charge for the same media. Fetch the receipt from result_url, take primary_output_url or the artifacts[] URLs, and store them.
If you do need the typed object, you can continue the earlier run as one more turn of the same conversation with previous_run_id, and ask for the missing fields. It is still a new run, so give it a new idempotency key and its own cap.
| Field on the receipt | Where it helps |
|---|---|
primary_output_url | The one URL you serve, when you set primary_output_key |
artifacts[] | All media the run made, also on a failed run |
output_error | Why the projection did not match your schema |
usage | What the run billed, also on a degraded run |
Fix the schema, not the run
A degraded run points at a schema that asks for more than the Format can give. Loosen the required keys, or use primary_output_key so primary_output_url still resolves. The rules and failure modes are on the structured output page.
If the payload is over 1 MiB, payload is null, error.code is payload_too_large, and error.result_url is where you fetch it.
Related posts
More in Formats
- Format status_url never holds output: poll it, then fetch the result
A Sume Format run's status_url returns a small poll payload with no output or artifacts. Poll it with backoff up to expires_at, then call result_url once.
- sume-virtual-try-on or sume-virtual-fitting: read the io profile first
Two catalog Formats sound alike. Before you send a model photo and a garment to either, read GET /v1/formats/sume/{slug} for its io profile and spend cap.
- Stalled or just slow? Read the Sume Format run events feed
A Format run takes minutes. GET /v1/format-runs/{id}/events shows the phase, and the last entry's at timestamp is a progress clock. If it stops, it is stalled.
- Turn a new video model launch into a repeatable Sume Format
A new video model dropped and one test clip looked great. Save the recipe as a Format, call it with a key, a cap and a webhook, and keep the result repeatable.
Written by Sume