Selling Video Generation to Agents, for USDC
We put our video generation engine behind an x402 endpoint. An agent POSTs a prompt, its wallet pays the 402 challenge in USDC, and it gets a video back. No account, no API key, no signup. Here is what we learned building it: wallet-keyed accounts, pricing an endpoint honestly, dynamic prices the SDK could not see, and two bugs that failed open.

We put video generation behind an x402 endpoint. An agent POSTs a prompt to https://www.trezalabs.com/api/x402/video, its wallet signs the 402 payment challenge, and it gets back a link to poll for the finished file. There is no account, no API key, and no signup anywhere in the flow. The payment is the entire relationship.
The demo page has a clip a wallet actually bought and a link to the settled Base transaction, if you would rather check than take our word for it.
This post is the engineering write-up: what x402 selling actually involves, the parts that were harder than the quickstart suggests, and two bugs that failed open. If you are building an x402 seller, this is the post we wish we had read first.
What x402 is, in one paragraph
x402 is a payment protocol built on the HTTP 402 Payment Required status code. A paid endpoint answers an unpaid request with a 402 carrying structured payment requirements: the price, the token (USDC), the network, and the address to pay. The client signs a payment authorization with its wallet and retries the request with the payment attached. The server verifies and settles the payment through a facilitator, then serves the resource. No account creation, no API key, no card form, which is exactly what an autonomous agent needs, because an agent can hold a wallet but cannot pass a KYC flow or read a confirmation email.
The thing that was broken
We had x402 support before this, on exactly one endpoint: credit top-ups. An agent could pay USDC to add credits to a Treza account. The problem is who x402 discovery actually delivers. An agent that finds you through the x402 Bazaar has a wallet and nothing else. No Treza account, no bearer token, and no way to get either without a human. So the only thing it could buy from us was a top-up for an account it did not have. Discovery was delivering precisely the callers who would hit that wall.
The fix was to sell the product itself, not a balance: one endpoint where the payment alone buys a render.
The market, measured from the outside
Before building, we scanned all 15,308 listings in the CDP Bazaar. Of the 123 that matched "video", every single one was a TikTok or YouTube scraper or a transcript fetcher, plus one generic fal.ai passthrough. Image generation was crowded. Video generation was an empty category.
That scan also taught us how discovery works from the buyer's side, which shaped the listing itself. Agents search the Bazaar by intent, so your listing description and tags are your search surface. Ours states the price for every size, the input schema, and the fact that no account or key is needed, because those are the things an agent's planner checks before committing a wallet.
What we sell, and how it is priced
The endpoint sells clips at 5, 10, or 15 seconds, in 16:9 or 9:16. Each combination is its own published pipeline on the same engine every human customer runs on. That sounds repetitive, and it buys something worth having: every price an agent is quoted maps to a frozen, versioned, auditable graph, and adding a size never touches the execution path production pipelines depend on.
Pricing took two attempts, and the first one was wrong in an instructive way. The flat price we shipped first was picked to clear an internal credit gate, not to match a cost. The first real purchase proved it wrong immediately: $1.16523 of provider cost against $3.50 taken. That is not a margin, it is a mispricing that punishes exactly the small purchases x402 exists for.
So we measured. Across 71 successful production runs on three pipelines whose cost is dominated by a single video generation, the provider cost came out at $0.233 per generated second, and remarkably tight: the three pipelines agreed to within $0.002 per second. The price is now that measured rate times the same markup every human customer pays. Charging agents a different multiple than humans would need a reason, and there is not one: the render is identical work. The number moves when the model's price moves, not when we feel like it.
A settled payment becomes an identity
There are no accounts in the flow, but there is an account in the system, and when it gets created matters more than we expected.
When a payment settles, we create (or find) an account keyed to the payer's wallet address, credit it with the paid amount, and let the run spend from that balance at the normal rate. This keeps one accounting path for all revenue rather than a special case beside the ledger. It also means overpayment is not lost: whatever the render does not consume stays on the wallet's balance for its next call.
The ordering rule is the security property: the account is created only after the money moves. Our accounts come with welcome credits, so an endpoint that minted an account on first contact would let an unpaid caller farm free credits by probing with fresh wallet addresses. The account-creation line runs strictly after settlement, and each settled transaction hash grants credits exactly once, so a retried request cannot double-credit.
One honest caveat we designed around rather than solved: leftover balance on a wallet-keyed account is close to worthless to the payer, because the next call charges in full again and there is no API key with which to spend the remainder any other way. Pricing per second and rounding up only to the cent keeps that stranded remainder in pennies rather than dollars.
Dynamic pricing when the price hook cannot see the body
A 15-second clip costs three times what a 5-second one does, so the 402 challenge has to quote the price for what this caller actually asked for. The x402 SDK supports dynamic pricing, but its price hook receives an HTTP context with the URL, headers, and query string, and never the body, which has already been consumed by the time pricing runs.
The workaround the API shape deserves is not "put your arguments in the query string of a POST". Since we own both the wrapper and the price function, we parse the body once in the wrapper, stash it in an AsyncLocalStorage scoped to the request, and let the price function read it from there. The agent gets one place to put its arguments, and pricing sees them anyway.
Two properties keep this safe:
- The price function must be a pure function of the body. It runs on the unpaid request to build the challenge and again on the paid retry, and a payment that does not match the re-derived requirements is refused. That is what stops a caller quoting a five-second clip and redeeming the payment for a thirty-second one.
- It must never throw. Pricing runs before validation has had a chance to refuse the request, so a nonsense body has to yield some price. Validation then turns the nonsense into a 400, and it does so after the payment verifies but before it settles, so a caller who asked for a size we do not sell is turned away with their money untouched.
Renders outlast requests
A video render takes minutes, far longer than a request can reasonably be held open, and an agent has nowhere to receive a webhook. So a paid POST answers 202 Accepted with a run id and a signed ticket embedded in a status URL. The ticket is the payer's only proof of purchase: GET with it returns the run's status and, once finished, the video URL. Polling is free, because the render behind it is already paid for.
Two things that failed open
Both of these shipped, briefly. Both are the kind of bug an x402 seller should go looking for, because the failure mode is not an error, it is money-shaped silence.
An unmatched route pattern serves the resource for free. The SDK accepts either a bare route config or a map keyed by route pattern. A bare config registers under *, which the Bazaar extension normalizes to a generated placeholder, so the listing described our route under a template name and requests did not match the paid pattern. Name your route pattern explicitly, and test the unpaid path: the correct behavior of a paid endpoint with no payment attached is a 402, not a render.
A credit precheck refused a payment that had already settled. Our engine normally asserts that an account has enough balance before starting a run, using a conservative internal estimate. Running that gate on the x402 path let the estimate refuse a settled payment, which is the worst outcome available: money taken, nothing delivered. The x402 caller has already paid for exactly this run, so the precheck does not apply. The general rule: any guard designed for "might not pay" logic must be re-examined on a path where payment has already happened.
A third ordering decision prevents a subtler version of the same class. The usual x402 pattern is verify, run the handler, then settle. Our handler grants prepaid credits, and an exact payment can verify but fail to settle if the payer moves the funds in between, which under settle-last hands out free credits on every retry. We settle before the handler runs: money on-chain first, ledger second.
Getting found
The last surprise is that distribution for an agent-facing API looks nothing like distribution for a developer-facing one. There is no docs page an agent reads. There are three surfaces:
- The Bazaar listing. Machine-readable description, input schema, output example, tags. This is your SEO, and the ranking signal is whether an agent that buys once comes back.
- Directories and curated lists. x402-list.com, the awesome-x402 lists on GitHub, and the ecosystem indexes. Humans building agents browse these.
- LLM answers. Developers ask their assistant how to generate video over x402. Being present in the docs, directories, and posts the models read is what gets you into the answer.
If you want the buyer's-side view of the endpoint, the x402 video generation API page covers sizes, prices, and the exact request flow, and the demo page has the live proof.
Try it
curl -X POST https://www.trezalabs.com/api/x402/video \
-H "Content-Type: application/json" \
-d '{"prompt": "a manta ray gliding over a sunlit coral reef, slow cinematic drift", "seconds": 5, "aspectRatio": "16:9"}'Unpaid, that returns the 402 challenge with the exact price for the clip you described. With an x402 client and a funded wallet, the same request returns a status URL, and a couple of minutes later, a video.
There is a complete, runnable buyer script at treza-labs/x402-video-example, including one gotcha worth knowing before you write your own: the x402 client SDK ships with a default spend control that caps any single payment at $1, which silently rejects every clip on this endpoint until you raise it.


