Media tools

Validate video timeline JSON before rendering: Sume's plan preflight

POST /v1/timeline-1.0/plan checks a Timeline 1.0 document without billing and returns its duration, billable minutes, cost estimate, and filtergraph summary.

6 min readSume
All posts

To validate a video timeline before rendering it with the Sume API, send the same JSON body to POST /v1/timeline-1.0/plan. The plan runs the schema, the Sume-host URL checks, and the compiler, then returns a timeline_plan with the duration, billable minutes, a cost estimate, and a filtergraph summary. It creates no job, reserves no credits, and downloads no media.

The facts below come from the Timeline 1.0 docs and the plan entry in the Sume API reference, read on 2026-09-26. Anything described as current behavior is read from Sume's code. The render itself is covered in How to assemble a long-form video.

How do I call the plan?

Send the render body unchanged: the plan takes the same request schema as POST /v1/timeline-1.0/render. It authenticates with your API key like any other route, and Idempotency-Key is not required. A valid document answers 200; one that breaks a rule answers 400 with a stable error code.

curl -X POST https://api.sume.com/v1/timeline-1.0/plan \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audio": {
      "url": "https://media.sume.com/artifacts/artf_demo/voice.wav",
      "duration_seconds": 95
    },
    "video": [
      { "source_url": "https://media.sume.com/artifacts/artf_demo/intro.mp4", "start": 0, "duration": 40 },
      {
        "source_url": "https://media.sume.com/artifacts/artf_demo/talk.mp4",
        "start": 40,
        "duration": 55,
        "transition": { "type": "fade", "duration": 0.25 }
      }
    ]
  }'

What does the plan return?

Every field below is required in the response. For the 95-second spine above, billable_minutes is 2. Because output.fps is omitted, the current code compiles that plan at 30 fps, where the 0.25 s fade is 7.5 frames, so warnings[] carries transition_snapped_to_frame.

The timeline_plan object, from Timeline 1.0 and the Sume API reference, read 2026-09-26.
FieldWhat it holds
objecttimeline_plan.
validA boolean. In the current code a failing document gets a 400, not a plan.
duration_secondsThe output length, audio.duration_seconds.
segment_countThe number of compiled video[] slots.
coverage_secondsIn the current code, where the last slot ends: its start plus duration.
billable_minutesIn the current code, ceil(duration_seconds / 60), at least 1.
estimated_cost_usd_microsThe cost estimate, in millionths of a US dollar.
transition_countTransitions compiled into the graph.
filtergraph_summaryA string summarizing the compiled graph.
warnings[]Each with code and message, plus optional segment_index and context.

What does the plan check?

It runs the schema and admit bounds, the Sume-host check on every URL, and the compiler: the program rules and refusal codes listed in How to assemble a long-form video, from timeline_must_start_at_zero to too_many_chained_transitions. In the current code the plan and the render call the same validators, so a 400 from the plan carries the code the render would return.

What can't the plan predict?

The plan never downloads media, so anything that depends on the files shows up only when you render:

  • Short sources. A slot longer than its clip is padded or looped, and a boundary without enough footage for its transition becomes a hard cut. The docs say a plan cannot predict these warnings.
  • A soundtrack shorter than the output (soundtrack_shorter_than_spine): in the current code the plan does not know the bed's length.
  • Frame rate. With output.fps omitted, the current code compiles the plan at 30 fps, while the render uses the rate its sources run at, so transition snapping can differ. Set output.fps to make them match.
  • Missing clips. In the current code the plan checks each URL's host but skips the request the render makes to each file, so a missing clip passes the plan and the render refuses it with source_not_found.

Does the estimate match what the render reserves?

Not exactly. In the current code estimated_cost_usd_micros is billable_minutes at the list rate, $0.10 per output minute on API pricing, with no agent fee. The render reserves ceil(audio.duration_seconds / 60) minutes at that rate plus a 5.5% agent fee by default. The docs say to confirm the rate live in GET /v1/catalog.

The plan also does not check your balance: only the render can answer 402 for insufficient funds. For other ways to see a price before you spend, see how to estimate AI video cost.

How do I use the plan in a pipeline?

Treat it as an unbilled compile step between building the document and paying for it:

  • Build the document and call the plan.
  • On a 400, fix the field the error names and plan again. Nothing was billed.
  • Check duration_seconds, coverage_seconds, and billable_minutes against what you meant to render, and read warnings[]. Snapped transitions and ignored still motion are soft warnings, not failures.
  • Render with an Idempotency-Key, then read the result's warnings[] for the file-dependent cases the plan cannot see.

Sources

Related posts

Written by Sume