---
title: "Generate a video from a prompt or a first frame, paid with x402"
method: POST
path: "/api/x402/video"
---

# Generate a video from a prompt or a first frame, paid with x402

`POST /api/x402/video`

## Request

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

Generates a video from a text prompt, or animates an image_url from its first frame. The paid response waits about as long as the chosen model usually takes to render and returns the video URL when it is ready, otherwise a statusUrl to poll. Pay via the x402 protocol (HTTP 402): call without payment to receive a 402 challenge carrying the price and networks, sign the payment with an x402-capable wallet, and retry with the payment-signature header. The challenge lists two ways to pay the same amount: USDC on Base (eip155:8453) first, then USDC on Solana mainnet (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp); a client pays the first one it has a wallet for, and neither needs gas money in the paying wallet. No account, API key, or signup is involved, so the payment is the only credential. A payment presented again after it was redeemed is refused with a 409 (code PAYMENT_ALREADY_REDEEMED) rather than rendered twice. Price is quoted per request and depends on the model and clip length you pick, so send the model and seconds you want and read the amount off the challenge rather than assuming a fixed figure; GET this path with no parameters for every model and price. Only the documented fields are read; a request carrying any other field is refused with a 400 before any payment moves, so nothing you send can be silently ignored and paid for. An image_url with no model renders on the cheapest model that takes an image at the length you ask for (wan-2.7 for 5 or 10 seconds, kling-3.0 for 15, veo-3.1-lite for 4, 6 or 8 or when you name no length), and the 402 quotes that model's price. The image is fetched and checked before the payment settles, so an image that cannot be used is refused (400, or 413 when it is too large, with a code) with nothing charged.

The paid call answers 200 at once, with the statusUrl in the X-Status-Url header, then sends a whitespace byte every 2 seconds while it waits (JSON parsers ignore it), and ends with one complete JSON object. By default it waits about as long as that model and length usually take to render (at least 45 seconds, about two and a half minutes for 5 seconds on minimax-h3, up to about ten for 15 seconds on seedance-2.5), so a caller that does not poll still gets its video; a client with a fixed timeout, such as the 60 seconds MCP tool calls allow, should send Prefer: wait=N or respond-async, and has the statusUrl in the X-Status-Url header either way. Read status in that body: success carries the video URL; error or partial means the render failed, nothing was charged for it, and retryUrl renders again on the wallet balance the payment left, without paying twice; running means the render is still going, so poll statusUrl. Send Prefer: wait=N to wait up to N seconds instead (at most 720). To get the statusUrl at once, send "async": true in the body or the header Prefer: respond-async, and the call answers at once, still 200, with status running, a runId and a statusUrl to poll. Preference-Applied echoes the preference that was honoured. Lost the statusUrl? GET /api/x402/purchases and sign its challenge with the paying wallet (Sign-In-With-X, free) to list every purchase with its file and a fresh statusUrl. And the same request sent again from the same wallet, once the first render has finished and its file never reached you, is answered with that file (alreadyPaid, paidUsd 0) and the new payment is not taken.

## Headers

- `Prefer` string

## Request body

- object
  - `prompt` string, required: What the video should show.
  - `seconds` 4 | 5 | 6 | 8 | 10 | 15: Clip length. Each model sells its own lengths: minimax-h3, kling-3.0, kling-3.0-pro and seedance-2.5 take 5, 10 or 15; veo-3.1-lite, veo-3.1-fast and veo-3.1 take 4, 6 or 8; wan-2.7 takes 5 or 10. Defaults to the model's shortest. A length the model does not sell is refused with a 400 before payment. The 402 challenge quotes the price for the length and model you send.
  - `aspectRatio` '16:9' | '9:16': Landscape or vertical. An image_url is cropped to it (1280x720 or 720x1280).
  - `model` 'minimax-h3' | 'veo-3.1-lite' | 'wan-2.7' | 'veo-3.1-fast' | 'kling-3.0' | 'kling-3.0-pro' | 'seedance-2.5' | 'veo-3.1': Which video model renders it. Without an image_url it defaults to minimax-h3. With an image_url and no model, the cheapest model that takes an image at the length you ask for: wan-2.7 for 5 or 10 seconds, kling-3.0 for 15, veo-3.1-lite for 4, 6 or 8 or no length. minimax-h3 (MiniMax Hailuo 3, default): 768p with native audio and the fewest prompt refusals; text only, so it cannot take an image_url; $0.42 for 5s, $0.84 for 10s, $1.26 for 15s. veo-3.1-lite (Google Veo 3.1 Lite): Google Veo at its lowest price with native audio, usually ready in about a minute; Google's safety filter returns nothing for some prompts, which is not charged and can be retried; $0.45 for 4s, $0.68 for 6s, $0.90 for 8s. wan-2.7 (Alibaba Wan 2.7): native audio, and the most permissive with photos of real people as a first frame; $0.70 for 5s, $1.40 for 10s. veo-3.1-fast (Google Veo 3.1 Fast): sharper Veo with native audio, typically under two minutes; $0.68 for 4s, $1.01 for 6s, $1.35 for 8s. kling-3.0 (Kling 3.0): 720p with native audio; $0.89 for 5s, $1.77 for 10s, $2.65 for 15s. kling-3.0-pro (Kling 3.0 Pro): Kling's higher-fidelity tier, 720p with native audio; $1.18 for 5s, $2.36 for 10s, $3.53 for 15s. seedance-2.5 (ByteDance Seedance 2.5): 720p with native audio and higher fidelity, with a strict content filter that refuses prompts resembling a brand, a broadcast, or a famous scene, and photos of real people; a refusal is not charged and the response carries a retryUrl; $1.64 for 5s, $3.27 for 10s, $4.90 for 15s. veo-3.1 (Google Veo 3.1): Google's top-fidelity model with synchronized audio, for final shots; $2.24 for 4s, $3.36 for 6s, $4.48 for 8s. Every model except minimax-h3 takes an image_url. GET this path with no parameters for the current list and prices. An unknown value is refused with a 400 before payment.
  - `image_url` string, uri: Optional first frame (image-to-video): a public https URL to a JPEG, PNG or WebP of up to 10 MB. It is cropped to the aspectRatio you buy (1280x720 or 720x1280) and the video starts from it. Every model except minimax-h3 takes one. Sent without a model it renders on the cheapest model that takes an image at the length you ask for (wan-2.7 for 5 or 10 seconds, kling-3.0 for 15, veo-3.1-lite for 4, 6 or 8 or no length), and the 402 challenge quotes that price. Sent with model minimax-h3 it is refused with a 400 (code IMAGE_MODEL_MISMATCH) before payment. The image must be fetchable within 4 seconds. It is fetched and checked before the payment settles, so one that cannot be used costs nothing.
  - `async` boolean: true answers at once (200, status running) with a runId and a statusUrl to poll, instead of waiting for the render. The header Prefer: respond-async does the same.

## Response `200`

Payment settled and the response waited for the render, by default about as long as that model usually takes (at least 45 seconds), or the Prefer: wait=N asked for. Streamed: the status and headers are sent at once (X-Status-Url carries the statusUrl), then a whitespace byte every 2 seconds, which JSON parsers ignore, then one complete JSON object. The HTTP status is 200 whatever happened to the render, so read status in the body: success carries video; error or partial carries the failure fields and a retryUrl, and nothing was charged for the render; running means the render is still going (it outlasted the wait, or is being collected from the provider, see note), so poll statusUrl.

- object
  - `runId` string
  - `status` 'success' | 'error' | 'partial' | 'cancelled' | 'running': success carries video. error, partial, and cancelled carry the failure fields. running means the render is still going; poll statusUrl.
  - `startedAt` string, date-time
  - `finishedAt` string, date-time
  - `durationMs` integer
  - `video` string, uri: URL of the finished video. Use it exactly as given: its query parameters, including c=, are part of it.
  - `outputs` object
    - `video` string, uri
  - `statusUrl` string, uri: The run's status, now and later. It carries its own signed claim ticket, so it needs no other credential. Use it exactly as given: the runId contains '#', encoded as %23.
  - `pollAfterMs` integer: Only when status is running: how long to wait before polling statusUrl.
  - `note` string: Set while a render that outlasted its deadline is being collected from the provider: status stays running until the clip arrives, then turns success with the video. Retry is refused meanwhile.
  - `alreadyPaid` object: Only when this wallet already bought this exact request and its file never reached it: the earlier purchase this answer hands over (runId, boughtAt). The new payment was not taken and paidUsd is 0.
    - `runId` string
    - `boughtAt` string, date-time
  - `paidUsd` number: What this request cost, quoted from its model and clip length.
  - `model` string: The model that rendered it, echoed from the request or the default.
  - `seconds` number
  - `aspectRatio` string
  - `imageUrl` string, uri: The image_url you sent, when you sent one.
  - `network` string: Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana).
  - `transaction` string: The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana.
  - `payer` string: The paying wallet: a 0x address on Base, a base58 address on Solana.
  - `error` string: Failed runs only. What went wrong, in the provider's words.
  - `failedStep` string: Failed runs only. The step that failed, e.g. Video Generation.
  - `reason` 'prompt_refused' | 'render_failed': Failed runs only. prompt_refused: the model declined the prompt or image, so reword it. render_failed: the render did not finish; the same prompt may succeed.
  - `retryable` boolean: Failed runs only. True until the run's one free retry has been used.
  - `retryUrl` string, uri: Failed runs only. POST here to render again on the wallet balance, optionally with {"prompt": "..."} in the body. Carries the same signed ticket.
  - `message` string: Failed runs only. What happened and what to do, in one sentence.
  - `balanceUsd` number: Failed or still-running runs: credit left on the paying wallet, which a retry spends.

## Other responses

- `400`: Missing or oversized prompt, a length or model that is not on sale, an image_url sent with model minimax-h3, an image_url that cannot be used, or a field this endpoint does not read. Refused before settlement, so nothing was charged; the body names what was wrong and what is accepted. Every image_url problem carries a code: IMAGE_URL_INVALID (not a plain https URL: http, a username or password, or a non-default port), IMAGE_URL_BLOCKED (a private, loopback or internal host), IMAGE_MODEL_MISMATCH (sent with minimax-h3, which is text only), IMAGE_FETCH_FAILED (the download failed or took over 4 seconds), IMAGE_UNSUPPORTED_TYPE (not a JPEG, PNG or WebP), or IMAGE_DIMENSIONS.
- `402`: Payment required: the challenge response carrying x402 payment instructions
- `409`: This payment was already redeemed for a render (code PAYMENT_ALREADY_REDEEMED). Nothing new was rendered or charged; send a new payment to buy another clip.
- `413`: image_url is larger than 10 MB (code IMAGE_TOO_LARGE). Refused before settlement, so nothing was charged.
- `429`: Rate limit exceeded
- `503`: Pay-per-video is not enabled on this deployment, or is temporarily unavailable. An image_url that could not be stored on our side answers 503 with code IMAGE_STORE_FAILED; nothing was charged.

---

[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)
