---
title: "Render a failed pay-per-video run again, without paying twice"
method: POST
path: "/api/x402/video/retry"
---

# Render a failed pay-per-video run again, without paying twice

`POST /api/x402/video/retry`

## Request

- Base URL: `https://www.trezalabs.com`
- URL: `https://www.trezalabs.com/api/x402/video/retry`
- Auth: HTTP bearer

A render that failed after the payment settled (the model refused the prompt, or the render did not finish) charged nothing, so the payment is still on the wallet's balance. POST here with the failed run's claim ticket to render again on that balance. Send {"prompt": "..."} to reword the prompt, which is the fix for prompt_refused; send no body to render the original prompt again. The retry renders the same model, length, aspect ratio, and first frame the failed run was sold. Each failed run can be retried once. Like the paid POST, it waits by default about as long as that model usually takes to render (Prefer: wait=N, at most 720) and ends with the result, or with status running and a statusUrl, in the same streamed 200 response; send "async": true, or the header Prefer: respond-async, for an immediate answer (200, status running) with a new runId and statusUrl. A run whose render outlasted its deadline is refused (409) while its clip is being collected.

## Query parameters

- `runId` string, required
- `token` string, required

## Headers

- `Prefer` string

## Request body

- object
  - `prompt` string: A reworded prompt. Omit to render the original again.
  - `async` boolean: true answers at once (200, status running) with a statusUrl to poll, instead of waiting for the render.

## Response `200`

The retry started and the response waited for it, by default about as long as that model usually takes to render, streamed the same way as the paid POST (X-Status-Url header at once, a whitespace byte every 2 seconds, then the JSON). Same shape as the paid POST's 200, plus retryOf (the failed run) and paidUsd 0. Read status in the body: success carries video; running means the render is still going, so poll statusUrl.

- object
  - `runId` string
  - `status` 'success' | 'error' | 'partial' | 'cancelled' | 'running'
  - `video` string, uri: URL of the finished video. Use it exactly as given.
  - `outputs` object
  - `statusUrl` string, uri
  - `retryOf` string
  - `paidUsd` number
  - `model` string: The model the failed run was sold on; the retry renders on the same one.
  - `seconds` number
  - `aspectRatio` string
  - `error` string: If the retry failed too: what went wrong, in the provider's words.
  - `reason` 'prompt_refused' | 'render_failed'
  - `message` string
  - `balanceUsd` number

## Other responses

- `400`: runId and token are required, or the prompt sent is empty or too long
- `402`: The wallet balance no longer covers a render of this size; the body carries balanceUsd, priceUsd, and payUrl for a new paid request
- `403`: Invalid, expired, or mismatched token
- `404`: Run not found, or the ticket is not for a pay-per-video run
- `409`: The run is still rendering, is still being collected from the provider, finished successfully, or has already been retried. An already-retried run's 409 carries retriedBy (the retry's runId and statusUrl): poll that statusUrl for the result.
- `429`: Rate limit exceeded
- `503`: Pay-per-video is temporarily unavailable

---

[API](https://skmtc.dev/trezalabs/apis/treza-pipelines-api.md) · [All operations](https://skmtc.dev/trezalabs/apis/treza-pipelines-api/llms.txt) · [OpenAPI document](https://skmtc.dev/trezalabs/apis/treza-pipelines-api/revisions/a76574c1731b?raw)
