Zura · Accept Draft for Final
The two-field handshake that stops you paying twice
- clip_details
- STRING
Seedance is a closed model on a metered API: a draft call costs something, the 1080p final costs more, and neither is refundable. That makes "did I actually watch this before I paid for a final?" a real question with a real answer - and GenjApproveDraft ("Zura · Accept Draft for Final") is where the answer is enforced.
It's a tiny node with a blunt job: check that the draft task ID you're about to escalate belongs to the shot you reviewed, and only then hand the ID onward.
The mechanism
Required inputs, all three: draft_task_id (STRING), accepted_draft_id (STRING), and clip_details (GENJ_CLIP).
Two things have to be true before it returns anything:
- You typed the same ID twice.
Watch the draft first, then paste its task ID into both fields to authorise the separately billed 1080p final.Empty or mismatched values raise. This is a deliberate speed bump: the act of copying the ID a second time is the "yes, I looked at it" gesture, in the same spirit as typing a repo name to delete it. - A receipt exists for that ID, and it names this shot.
GenjRecordDraftwroteoutput/genj/drafts/<sha256(draft_task_id)>.jsonbinding the task ID to the reviewed shot. The node reads it and compares itsshot_idagainstclip_details["review_shot_id"]. If the file isn't there, or the shot doesn't match, you getThis draft does not belong to the approved shot. Use the source shot ID recorded when the draft completed.
If both pass, it returns the task ID as a STRING, which is exactly what the draft-to-final API node wants as its draft_task_id.
The node itself calls nothing and bills nothing. It's a gate, and the billing happens in the node downstream - which is the correct place to put the guard rail, because the guard rail is now impossible to skip by accident in a re-run.
Wiring it up
In the pack's Seedance final graph the chain is short:
GenjLoadReviewedShot ──clip_details──► GenjApproveDraft ──STRING──► ByteDance2DraftToFinalVideoNode ──► GenjRestoreSoundtrack
Note the clip_details wire. It's what binds the approval to a specific reviewed shot rather than to whatever project state exists at the time you press Run. If you've re-prepared the shot since making the draft, the IDs won't line up and the node refuses - correctly, because the mask or the audio changed and the 1080p final would be rendered against something you never watched.
Install
ComfyUI Manager → search Zura Nodes → install → restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/ZURAVFX/ComfyUI_zura_nodes
python -m pip install -r ComfyUI/custom_nodes/ComfyUI_zura_nodes/requirements.txt
Nothing to download for this node. The paid half of the route is ComfyUI's official API nodes, which need a Comfy account with prepaid credits and run on the provider's queue. Practical advice from the wider ecosystem: batch your drafts, approve the one you actually like, and read the price the provider shows before the final - the video jobs are where a session gets expensive fast.
Gotchas
- Two fields is one field too many for some people. Yes, it's mildly annoying. It's also why you can't accidentally spend the final render price by re-queuing a workflow at 2 a.m.
- The draft record lives in
output/genj/drafts/. Wipe that folder and approval becomes impossible, even for a perfectly good draft. Keep it until the final is delivered. - Clip length is the provider's call. The pack's own docs are careful here: check the provider's supported clip length and displayed price rather than assuming your five-second reviewed shot is exactly what gets billed.
- Local engines have no equivalent. Wan, H3 and LTX have no per-call cost, so there's no approval gate - which is why the two-stage draft/final structure only exists on the Seedance branch.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| draft_task_id | STRING | — | |
| accepted_draft_id | STRING | — | |
| clip_details | GENJ_CLIP | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| STRING | STRING | — |