{"openapi":"3.0.0","info":{"title":"Treza Pipelines API","description":"Treza builds video pipelines from a description. Build pipelines that chain the best AI models (Veo 3.1, Seedance 2.5, Gemini Image, Llama, and any open model) with voiceover, captions, sequencing, and publishing, then call any published pipeline as an API.\n\nTwo ways to call a published pipeline:\n- Typed invoke: POST /api/pipelines/{id}/invoke with JSON inputs matching the pipeline's input contract. Returns a runId (HTTP 202); poll GET /api/pipelines/{id}/invoke?runId=... for status and outputs.\n- OpenAI-compatible: POST /api/pipelines/{id}/chat/completions works as a drop-in for any OpenAI SDK, streaming included. The latest user message feeds the pipeline and the output returns as the assistant message.\n\nAuthentication uses scoped API keys (Bearer treza_live_...) created in the Treza platform or minted over MCP (create_api_key). The key must own the pipeline and the pipeline must be published. Billing is prepaid credits that work across every model; check the balance at GET /api/account/balance, and top up either via a human at the dashboard or, on deployments with x402 enabled, agent-natively at POST /api/billing/credits/x402.\n\nAn MCP server is also available at https://www.trezalabs.com/api/mcp (streamable HTTP, OAuth 2.1) exposing tools to list, author, and publish pipelines, inspect runs, trigger new runs, check the credit balance, estimate run cost, and mint scoped API keys (create_api_key, for OAuth connections). Every one of those tools is also plain HTTP with an API key, listed below under /api/v1/tools/{name}: POST the tool's arguments as the JSON body, with the same key. They list your pipelines, runs and media library, author and publish pipelines, run them, and assemble or edit media.","version":"3.0.0","contact":{"name":"Treza Support","url":"https://www.trezalabs.com/support","email":"hello@trezalabs.com"}},"servers":[{"url":"https://www.trezalabs.com","description":"Production"}],"security":[{"bearerAuth":[]}],"paths":{"/api/pipelines/{id}/invoke":{"post":{"operationId":"invokePipeline","summary":"Invoke a published pipeline","description":"Starts a run of the deployed pipeline snapshot with the given inputs. Returns a runId immediately; poll the GET variant of this path for status and outputs.","parameters":[{"name":"id","in":"path","required":true,"description":"Pipeline id","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"inputs":{"type":"object","description":"Values keyed by the pipeline's input contract","additionalProperties":true}}}}}},"responses":{"202":{"description":"Run accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAccepted"}}}},"401":{"description":"Missing or invalid API key"},"402":{"description":"Insufficient prepaid credits. code is INSUFFICIENT_CREDITS; relay topUpUrl to a human, since credits are purchased through the signed-in dashboard.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["INSUFFICIENT_CREDITS"]},"balanceUsd":{"type":"number"},"topUpUrl":{"type":"string","format":"uri"}}}}}},"403":{"description":"Key does not own this pipeline"},"404":{"description":"Pipeline not found"},"409":{"description":"Pipeline is not published"}}},"get":{"operationId":"getInvokeRunStatus","summary":"Poll a pipeline run","description":"Returns the status of a run started via invoke, including outputs once the run completes.","parameters":[{"name":"id","in":"path","required":true,"description":"Pipeline id","schema":{"type":"string"}},{"name":"runId","in":"query","required":true,"description":"Run id returned by the invoke call","schema":{"type":"string"}}],"responses":{"200":{"description":"Run status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStatus"}}}},"401":{"description":"Missing or invalid API key"},"404":{"description":"Run not found"}}}},"/api/pipelines/{id}/chat/completions":{"post":{"operationId":"pipelineChatCompletions","summary":"OpenAI-compatible pipeline endpoint","description":"Drop-in replacement for the OpenAI chat completions API. Point any OpenAI SDK at this base URL with a Treza API key. The latest user message is fed into the pipeline and the pipeline output is returned as the assistant message. Supports streaming via server-sent events.","parameters":[{"name":"id","in":"path","required":true,"description":"Pipeline id","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messages"],"properties":{"messages":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string"},"content":{"type":"string"}}}},"stream":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Chat completion (JSON, or SSE when stream is true)"},"401":{"description":"Missing or invalid API key"},"402":{"description":"Insufficient prepaid credits. The OpenAI-style error object carries code INSUFFICIENT_CREDITS, balanceUsd, and a topUpUrl to relay to a human."},"403":{"description":"Key does not own this pipeline"},"404":{"description":"Pipeline not found"},"409":{"description":"Pipeline is not published"}}}},"/api/billing/credits/x402":{"post":{"operationId":"x402CreditTopUp","x-payment-info":{"protocols":["x402"],"price":{"mode":"fixed","currency":"USD","amount":"5.00"}},"summary":"Top up credits with an x402 payment (agent-native)","description":"Adds a fixed amount of Treza credit per call, paid 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. Grants are idempotent per settled transaction. A bearer (API key, OAuth token, or session) is optional and only chooses which account is credited; without one the credits are keyed to the paying wallet, so an agent holding nothing but a wallet can still fund itself. A Base wallet and a Solana wallet are separate accounts, and their balances are not shared. Only available when the deployment has x402 enabled; the balance endpoint's x402 field says so.","responses":{"200":{"description":"Credits granted","content":{"application/json":{"schema":{"type":"object","properties":{"grantedUsd":{"type":"number"},"duplicate":{"type":"boolean","description":"True when this payment was already credited (idempotent replay)."},"balanceUsd":{"type":"number"},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."}}}}}},"401":{"description":"A bearer was sent and is invalid, or the request carried neither a bearer nor a settled payer"},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions"},"429":{"description":"Rate limit exceeded"},"503":{"description":"x402 payments are not enabled on this deployment"}}}},"/api/x402/video":{"post":{"operationId":"x402GenerateVideo","x-payment-info":{"protocols":["x402"],"price":{"mode":"dynamic","currency":"USD","min":"0.42","max":"4.90"}},"summary":"Generate a video from a prompt or a first frame, paid with x402","description":"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.\n\nThe 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.","parameters":[{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits for the render, in seconds (at most 720), before it ends with status running and the statusUrl. Without it, the call 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 minutes for 15 seconds on seedance-2.5. A client with a fixed timeout, such as the 60 seconds MCP tool calls allow, should send wait=N under it or respond-async. Echoed in Preference-Applied."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"additionalProperties":false,"properties":{"prompt":{"type":"string","maxLength":2000,"description":"What the video should show."},"seconds":{"type":"number","enum":[4,5,6,8,10,15],"description":"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":{"type":"string","enum":["16:9","9:16"],"default":"16:9","description":"Landscape or vertical. An image_url is cropped to it (1280x720 or 720x1280)."},"model":{"type":"string","enum":["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"],"description":"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":{"type":"string","format":"uri","description":"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":{"type":"boolean","default":false,"description":"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."}}},"example":{"prompt":"a manta ray gliding over a sunlit coral reef, slow cinematic drift","seconds":5,"aspectRatio":"16:9","model":"minimax-h3"}}}},"responses":{"200":{"description":"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.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The statusUrl, sent before the render finishes, so a caller whose connection drops can still claim the video."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries video. error, partial, and cancelled carry the failure fields. running means the render is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"video":{"type":"string","format":"uri","description":"URL of the finished video. Use it exactly as given: its query parameters, including c=, are part of it."},"outputs":{"type":"object","properties":{"video":{"type":"string","format":"uri"}}},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"note":{"type":"string","description":"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":{"type":"object","description":"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.","properties":{"runId":{"type":"string"},"boughtAt":{"type":"string","format":"date-time"}}},"paidUsd":{"type":"number","description":"What this request cost, quoted from its model and clip length."},"model":{"type":"string","description":"The model that rendered it, echoed from the request or the default."},"seconds":{"type":"number"},"aspectRatio":{"type":"string"},"imageUrl":{"type":"string","format":"uri","description":"The image_url you sent, when you sent one."},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet: a 0x address on Base, a base58 address on Solana."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"failedStep":{"type":"string","description":"Failed runs only. The step that failed, e.g. Video Generation."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"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":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to render again on the wallet balance, optionally with {\"prompt\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed or still-running runs: credit left on the paying wallet, which a retry spends."}}}}}},"400":{"description":"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.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["IMAGE_URL_INVALID","IMAGE_URL_BLOCKED","IMAGE_MODEL_MISMATCH","IMAGE_FETCH_FAILED","IMAGE_UNSUPPORTED_TYPE","IMAGE_DIMENSIONS"]}}}}}},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions"},"409":{"description":"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.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["PAYMENT_ALREADY_REDEEMED"]},"transaction":{"type":"string"}}}}}},"413":{"description":"image_url is larger than 10 MB (code IMAGE_TOO_LARGE). Refused before settlement, so nothing was charged.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["IMAGE_TOO_LARGE"]}}}}}},"429":{"description":"Rate limit exceeded"},"503":{"description":"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."}}},"get":{"operationId":"x402GetVideoRun","summary":"Poll a video run bought with an x402 payment","description":"Returns the run's status and, once it has finished, the video URL. The paid POST returns the result when the render finishes within its wait, so this is for a run whose POST ended with status running, was sent async, or lost its connection: the POST's X-Status-Url header carries this URL from the first byte. The token is the claim ticket handed back by the paid POST, and is the only credential needed. Send the header Prefer: wait=N to hold the answer until the run finishes, for up to N seconds (at most 720), streamed the same way as the paid POST (a whitespace byte every 2 seconds, then the JSON); Preference-Applied echoes it. The video URL carries a download marker (c=) among its query parameters; use it exactly as given. A run that failed answers with the provider's error, a reason (prompt_refused or render_failed), and a retryUrl; nothing is charged for a failed render, so the payment stays on the wallet balance and the retry spends it. Once it has been retried, it answers with retriedBy (the retry's runId and statusUrl) instead, and retryable false. A render that outlasted its deadline reads running with a note while the clip is collected, then success with the video. Called with no query parameters at all, this returns the offer list instead: every model on sale with what it is for, whether it takes a first frame, and its prices, plus the request fields, the limits, and how to buy one.","parameters":[{"name":"runId","in":"query","required":false,"schema":{"type":"string"},"description":"Required to poll a run. Omit both this and token to get the offer list."},{"name":"token","in":"query","required":false,"schema":{"type":"string"},"description":"Signed claim ticket from the paid POST. Required to poll a run."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. wait=N holds the answer until the run finishes, for up to N seconds (at most 720), streamed as a whitespace byte every 2 seconds and then the JSON."}],"responses":{"200":{"description":"Run status plus the video URL once it has finished; or, when called with no query parameters, the offer list (service, description, howToBuy, fields, models[], offers[], limits, delivery)","content":{"application/json":{"schema":{"oneOf":[{"title":"Run status","required":["runId","status"],"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["running","success","partial","error","cancelled"],"example":"success","description":"running until the render finishes; success carries the video; partial, error, and cancelled carry the failure fields."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"pollAfterMs":{"type":"integer","description":"While running: how long to wait before polling again."},"video":{"type":"string","format":"uri","description":"URL of the finished video. Use it exactly as given: its query parameters, including c=, are part of it."},"outputs":{"type":"object"},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"failedStep":{"type":"string","description":"Failed runs only. The step that failed, e.g. Video Generation."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"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":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used, then false."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only, until retried. POST here to render again on the wallet balance, optionally with {\"prompt\": \"...\"} in the body. Carries the same signed ticket."},"retriedBy":{"type":"object","description":"Failed runs only, once retried: the retry. Poll its statusUrl for the result.","properties":{"runId":{"type":"string"},"statusUrl":{"type":"string","format":"uri"}}},"retriedByRunId":{"type":"string","description":"Failed runs only, once retried: the run the retry started (also retriedBy.runId)."},"note":{"type":"string","description":"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."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. Credit left on the paying wallet, which the retry spends."},"retryOf":{"type":"string","description":"Set on a run started by a retry: the failed run it replaces."}}},{"type":"object","title":"Menu","description":"Returned when the GET carries no query parameters: what is on sale, the prices, and how to buy.","required":["service","howToBuy","fields"],"properties":{"service":{"type":"string"},"description":{"type":"string"},"howToBuy":{"type":"object","description":"The request to send: method, an example body, and how payment works.","properties":{"method":{"type":"string"},"body":{"type":"object"},"note":{"type":"string"}}},"fields":{"type":"object","description":"Each request field and what it takes.","additionalProperties":{"type":"string"}},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"default":{"type":"boolean"},"firstFrame":{"type":"boolean","description":"Whether it takes an image_url."},"note":{"type":"string","description":"What it is for."},"prices":{"type":"array","items":{"type":"object","properties":{"seconds":{"type":"number"},"priceUsd":{"type":"number"}}}}}}},"offers":{"type":"array","description":"Every model, length and aspect ratio on sale, with its price.","items":{"type":"object","properties":{"model":{"type":"string"},"seconds":{"type":"number"},"aspectRatio":{"type":"string"},"priceUsd":{"type":"number"}}}},"limits":{"type":"object","description":"maxPromptChars, and the seconds, aspectRatio and model values on sale."},"delivery":{"type":"string","description":"How the paid call answers and where the file ends up."},"alreadyPaid":{"type":"string","description":"Where a file already paid for can be collected, for a buyer that lost its statusUrl."}}}]}}}},"400":{"description":"One of runId and token was given without the other. Supply both to poll a run, or neither to get the offer list. The runId contains '#', so a hand-built URL must keep it encoded as %23; use statusUrl exactly as returned."},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found"}}}},"/api/x402/video/retry":{"post":{"operationId":"x402RetryVideoRun","summary":"Render a failed pay-per-video run again, without paying twice","description":"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.","parameters":[{"name":"runId","in":"query","required":true,"schema":{"type":"string"},"description":"The failed run, from its status response."},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Signed claim ticket from the paid POST. The retryUrl in the status response carries both."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running); wait=N sets how long the call waits for the render, in seconds (at most 720). Without it, the call waits about as long as that model and length usually take to render, as the paid POST does, which can be several minutes."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","maxLength":2000,"description":"A reworded prompt. Omit to render the original again."},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll, instead of waiting for the render."}}}}}},"responses":{"200":{"description":"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.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The retry's statusUrl."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success"},"video":{"type":"string","format":"uri","description":"URL of the finished video. Use it exactly as given."},"outputs":{"type":"object"},"statusUrl":{"type":"string","format":"uri"},"retryOf":{"type":"string"},"paidUsd":{"type":"number","example":0},"model":{"type":"string","description":"The model the failed run was sold on; the retry renders on the same one."},"seconds":{"type":"number"},"aspectRatio":{"type":"string"},"error":{"type":"string","description":"If the retry failed too: what went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"]},"message":{"type":"string"},"balanceUsd":{"type":"number"}}}}}},"400":{"description":"runId and token are required, or the prompt sent is empty or too long"},"402":{"description":"The wallet balance no longer covers a render of this size; the body carries balanceUsd, priceUsd, and payUrl for a new paid request"},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found, or the ticket is not for a pay-per-video run"},"409":{"description":"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":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-video is temporarily unavailable"}}}},"/api/x402/speech":{"post":{"operationId":"x402GenerateSpeech","x-payment-info":{"protocols":["x402"],"price":{"mode":"dynamic","currency":"USD","min":"0.02","max":"1.26"}},"summary":"Generate a voiceover from text (ElevenLabs), paid with x402","description":"Speaks up to 3000 characters of text in one of over 30 ElevenLabs voices and returns an MP3, usually within seconds. Priced per character ($0.42 per 1,000, rounded up to the cent, minimum $0.02); the 402 quotes the exact figure for your text. 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). Only the documented fields are read; a request carrying any other field is refused with a 400 before any payment moves.\n\nThe 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. Read status in that body: success carries audio, the URL of the speech; error means it failed, nothing was charged for it, and retryUrl tries again on the wallet balance the payment left, without paying twice; running means it outlasted the wait (45 seconds by default, Prefer: wait=N for up to 720), so poll statusUrl. Send \"async\": true or Prefer: respond-async for an immediate answer (200, status running) with a statusUrl. 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.","parameters":[{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (default 45, at most 720), before it ends with status running and the statusUrl. Echoed in Preference-Applied."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"additionalProperties":false,"properties":{"text":{"type":"string","maxLength":3000,"description":"What to say."},"voice":{"type":"string","default":"george","description":"A voices[].id from GET /api/x402/speech, which lists every voice with what it sounds like: ElevenLabs' default voices and the library voices on our account, over 30 in all, e.g. george (warm UK storyteller), brian (deep US narrator), alice (clear UK educator). A voice whose menu entry carries a priceMultiplier costs that multiple of the per-character price, and the 402 quotes it. An unknown voice is refused with a 400 before any payment moves."},"model":{"type":"string","enum":["eleven-v3","eleven-multilingual-v2"],"default":"eleven-v3","description":"eleven-v3: the most expressive read, and performs inline directions in square brackets ([whispers], [excited]) without speaking them. eleven-multilingual-v2: the steadiest read for long narration, 29 languages."},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll."}}},"example":{"text":"Welcome back. Today we are looking at three small habits that make a big difference.","voice":"george","model":"eleven-v3"}}}},"responses":{"200":{"description":"Payment settled and the response waited for the speech. Streamed: the status and headers are sent at once (X-Status-Url carries the statusUrl), then a whitespace byte every 2 seconds, then one complete JSON object. The HTTP status is 200 whatever happened, so read status in the body.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The statusUrl, sent before the speech is ready, so a caller whose connection drops can still claim it."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries audio. error, partial, and cancelled carry the failure fields. running means it is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"audio":{"type":"string","format":"uri","description":"URL of the finished speech. Use it exactly as given: its query parameters, including c=, are part of it."},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"alreadyPaid":{"type":"object","description":"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.","properties":{"runId":{"type":"string"},"boughtAt":{"type":"string","format":"date-time"}}},"paidUsd":{"type":"number","description":"What this request cost."},"model":{"type":"string"},"voice":{"type":"string"},"characters":{"type":"integer","description":"Characters spoken: what the price was computed from."},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"Failed runs only. prompt_refused: the model declined the text, so reword it. render_failed: it did not finish; the same text may succeed."},"retryable":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to try again on the wallet balance, optionally with {\"text\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. The wallet balance the retry spends."}}},"example":{"runId":"2026-09-23T18:04:11.000Z#1a2b3c4d","status":"success","audio":"https://www.trezalabs.com/api/media/generated/x402_speech/1a2b3c4d.mp3?s=...&c=...","statusUrl":"https://www.trezalabs.com/api/x402/speech?runId=...&token=...","paidUsd":0.04,"model":"eleven-v3","voice":"george","characters":84,"transaction":"0x..."}}}},"400":{"description":"Missing or oversized text, a voice or model that is not on the menu, or a field this endpoint does not read. Refused before settlement, so nothing was charged."},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions"},"409":{"description":"This payment was already redeemed (code PAYMENT_ALREADY_REDEEMED). Nothing new was generated or charged; send a new payment to buy another."},"429":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-call speech is not enabled on this deployment, or is temporarily unavailable."}}},"get":{"operationId":"x402GetSpeechRun","summary":"Poll a speech bought with an x402 payment, or read the menu","description":"Called with no query parameters, returns the menu: what is on sale, every option and price, and how to buy. With runId and token (the statusUrl the paid POST handed back), returns the run's status and, once finished, audio: the file URL. The token is the claim ticket and the only credential needed. Send Prefer: wait=N to hold the answer until it finishes, for up to N seconds (at most 720), streamed like the paid POST. A failed run answers with the provider's error, a reason, and a retryUrl.","parameters":[{"name":"runId","in":"query","required":false,"schema":{"type":"string"},"description":"Required to poll a run. Omit both this and token to get the menu."},{"name":"token","in":"query","required":false,"schema":{"type":"string"},"description":"Signed claim ticket from the paid POST. Required to poll a run."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. wait=N holds the answer until the run finishes, for up to N seconds (at most 720)."}],"responses":{"200":{"description":"Run status plus audio once it has finished; or, with no query parameters, the menu.","content":{"application/json":{"schema":{"oneOf":[{"title":"Run status","required":["runId","status"],"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries audio. error, partial, and cancelled carry the failure fields. running means it is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"audio":{"type":"string","format":"uri","description":"URL of the finished speech. Use it exactly as given: its query parameters, including c=, are part of it."},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"paidUsd":{"type":"number","description":"What this request cost."},"model":{"type":"string"},"voice":{"type":"string"},"characters":{"type":"integer","description":"Characters spoken: what the price was computed from."},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"Failed runs only. prompt_refused: the model declined the text, so reword it. render_failed: it did not finish; the same text may succeed."},"retryable":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to try again on the wallet balance, optionally with {\"text\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. The wallet balance the retry spends."}}},{"type":"object","title":"Menu","description":"Returned when the GET carries no query parameters: what is on sale, the prices, and how to buy.","required":["service","howToBuy","fields"],"properties":{"service":{"type":"string"},"description":{"type":"string"},"howToBuy":{"type":"object","description":"The request to send: method, an example body, and how payment works.","properties":{"method":{"type":"string"},"body":{"type":"object"},"note":{"type":"string"}}},"pricing":{"type":"string","description":"How the price is computed."},"fields":{"type":"object","description":"Each request field and what it takes.","additionalProperties":{"type":"string"}},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"default":{"type":"boolean"},"note":{"type":"string"}}}},"voices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"description":{"type":"string"},"default":{"type":"boolean"}}}},"limits":{"type":"object","description":"maxTextChars."},"delivery":{"type":"string","description":"How the paid call answers and where the file ends up."},"alreadyPaid":{"type":"string","description":"Where a file already paid for can be collected, for a buyer that lost its statusUrl."}}}]}}}},"400":{"description":"One of runId and token was given without the other. The runId contains '#', so a hand-built URL must encode it as %23."},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found"}}}},"/api/x402/speech/retry":{"post":{"operationId":"x402RetrySpeechRun","summary":"Generate a failed speech again, without paying twice","description":"A speech that failed after the payment settled charged nothing, so the payment is still on the wallet's balance. POST here with the failed run's claim ticket to try again on that balance, with the same settings it was sold with. Send {\"text\": \"...\"} to reword it (the fix for prompt_refused), or no body to use the original. Each failed run can be retried once. Waits and streams like the paid POST; \"async\": true or Prefer: respond-async answers at once (200, status running).","parameters":[{"name":"runId","in":"query","required":true,"schema":{"type":"string"},"description":"The failed run, from its status response."},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Signed claim ticket. The retryUrl carries both."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (default 45, at most 720), before it ends with status running and the statusUrl. Echoed in Preference-Applied."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":3000,"description":"A reworded input. Omit to use the original."},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll."}}}}}},"responses":{"200":{"description":"The retry started and the response waited for it, streamed the same way as the paid POST. Same shape as the paid POST 200, plus retryOf and paidUsd 0.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The retry's statusUrl, sent before the render finishes."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}}},"400":{"description":"runId and token are required, or the text sent is empty or too long"},"402":{"description":"The wallet balance does not cover this retry; the body carries balanceUsd, priceUsd, and payUrl for a new paid request"},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found, or the ticket is not for a pay-per-call speech run"},"409":{"description":"The run is still going, finished successfully, or has already been retried"},"429":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-call speech is temporarily unavailable"}}}},"/api/x402/music":{"post":{"operationId":"x402GenerateMusic","x-payment-info":{"protocols":["x402"],"price":{"mode":"dynamic","currency":"USD","min":"0.06","max":"0.12"}},"summary":"Generate an original music track (Lyria 3), paid with x402","description":"Composes an original track from a description of genre, mood, instruments and tempo. lyria-3-clip: A 30-second piece: a bed for a short video, a sting, a loop. $0.06. lyria-3-pro: A full-length song with structure (intro, development, ending). $0.12. 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). Only the documented fields are read; a request carrying any other field is refused with a 400 before any payment moves.\n\nThe 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. Read status in that body: success carries audio, the URL of the track; error means it failed, nothing was charged for it, and retryUrl tries again on the wallet balance the payment left, without paying twice; running means it outlasted the wait (45 seconds by default, Prefer: wait=N for up to 720), so poll statusUrl. Send \"async\": true or Prefer: respond-async for an immediate answer (200, status running) with a statusUrl. 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.","parameters":[{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (default 45, at most 720), before it ends with status running and the statusUrl. Echoed in Preference-Applied."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"additionalProperties":false,"properties":{"prompt":{"type":"string","maxLength":2000,"description":"Genre, mood, instruments, tempo."},"model":{"type":"string","enum":["lyria-3-clip","lyria-3-pro"],"default":"lyria-3-clip"},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll."}}},"example":{"prompt":"warm lo-fi hip hop, soft piano chords, vinyl crackle, relaxed 80 bpm groove","model":"lyria-3-clip"}}}},"responses":{"200":{"description":"Payment settled and the response waited for the track. Streamed: the status and headers are sent at once (X-Status-Url carries the statusUrl), then a whitespace byte every 2 seconds, then one complete JSON object. The HTTP status is 200 whatever happened, so read status in the body.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The statusUrl, sent before the track is ready, so a caller whose connection drops can still claim it."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries audio. error, partial, and cancelled carry the failure fields. running means it is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"audio":{"type":"string","format":"uri","description":"URL of the finished track. Use it exactly as given: its query parameters, including c=, are part of it."},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"alreadyPaid":{"type":"object","description":"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.","properties":{"runId":{"type":"string"},"boughtAt":{"type":"string","format":"date-time"}}},"paidUsd":{"type":"number","description":"What this request cost."},"model":{"type":"string"},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"Failed runs only. prompt_refused: the model declined the prompt, so reword it. render_failed: it did not finish; the same prompt may succeed."},"retryable":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to try again on the wallet balance, optionally with {\"prompt\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. The wallet balance the retry spends."}}},"example":{"runId":"2026-09-23T18:04:11.000Z#1a2b3c4d","status":"success","audio":"https://www.trezalabs.com/api/media/generated/x402_music/1a2b3c4d.mp3?s=...&c=...","statusUrl":"https://www.trezalabs.com/api/x402/music?runId=...&token=...","paidUsd":0.06,"model":"lyria-3-clip","transaction":"0x..."}}}},"400":{"description":"Missing or oversized prompt, a model that is not on the menu, or a field this endpoint does not read. Refused before settlement, so nothing was charged."},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions"},"409":{"description":"This payment was already redeemed (code PAYMENT_ALREADY_REDEEMED). Nothing new was generated or charged; send a new payment to buy another."},"429":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-call music is not enabled on this deployment, or is temporarily unavailable."}}},"get":{"operationId":"x402GetMusicRun","summary":"Poll a track bought with an x402 payment, or read the menu","description":"Called with no query parameters, returns the menu: what is on sale, every option and price, and how to buy. With runId and token (the statusUrl the paid POST handed back), returns the run's status and, once finished, audio: the file URL. The token is the claim ticket and the only credential needed. Send Prefer: wait=N to hold the answer until it finishes, for up to N seconds (at most 720), streamed like the paid POST. A failed run answers with the provider's error, a reason, and a retryUrl.","parameters":[{"name":"runId","in":"query","required":false,"schema":{"type":"string"},"description":"Required to poll a run. Omit both this and token to get the menu."},{"name":"token","in":"query","required":false,"schema":{"type":"string"},"description":"Signed claim ticket from the paid POST. Required to poll a run."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. wait=N holds the answer until the run finishes, for up to N seconds (at most 720)."}],"responses":{"200":{"description":"Run status plus audio once it has finished; or, with no query parameters, the menu.","content":{"application/json":{"schema":{"oneOf":[{"title":"Run status","required":["runId","status"],"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries audio. error, partial, and cancelled carry the failure fields. running means it is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"audio":{"type":"string","format":"uri","description":"URL of the finished track. Use it exactly as given: its query parameters, including c=, are part of it."},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"paidUsd":{"type":"number","description":"What this request cost."},"model":{"type":"string"},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"Failed runs only. prompt_refused: the model declined the prompt, so reword it. render_failed: it did not finish; the same prompt may succeed."},"retryable":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to try again on the wallet balance, optionally with {\"prompt\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. The wallet balance the retry spends."}}},{"type":"object","title":"Menu","description":"Returned when the GET carries no query parameters: what is on sale, the prices, and how to buy.","required":["service","howToBuy","fields"],"properties":{"service":{"type":"string"},"description":{"type":"string"},"howToBuy":{"type":"object","description":"The request to send: method, an example body, and how payment works.","properties":{"method":{"type":"string"},"body":{"type":"object"},"note":{"type":"string"}}},"fields":{"type":"object","description":"Each request field and what it takes.","additionalProperties":{"type":"string"}},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"default":{"type":"boolean"},"note":{"type":"string"},"priceUsd":{"type":"number"}}}},"limits":{"type":"object","description":"maxPromptChars."},"delivery":{"type":"string","description":"How the paid call answers and where the file ends up."},"alreadyPaid":{"type":"string","description":"Where a file already paid for can be collected, for a buyer that lost its statusUrl."}}}]}}}},"400":{"description":"One of runId and token was given without the other. The runId contains '#', so a hand-built URL must encode it as %23."},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found"}}}},"/api/x402/music/retry":{"post":{"operationId":"x402RetryMusicRun","summary":"Generate a failed track again, without paying twice","description":"A track that failed after the payment settled charged nothing, so the payment is still on the wallet's balance. POST here with the failed run's claim ticket to try again on that balance, with the same settings it was sold with. Send {\"prompt\": \"...\"} to reword it (the fix for prompt_refused), or no body to use the original. Each failed run can be retried once. Waits and streams like the paid POST; \"async\": true or Prefer: respond-async answers at once (200, status running).","parameters":[{"name":"runId","in":"query","required":true,"schema":{"type":"string"},"description":"The failed run, from its status response."},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Signed claim ticket. The retryUrl carries both."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (default 45, at most 720), before it ends with status running and the statusUrl. Echoed in Preference-Applied."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","maxLength":2000,"description":"A reworded input. Omit to use the original."},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll."}}}}}},"responses":{"200":{"description":"The retry started and the response waited for it, streamed the same way as the paid POST. Same shape as the paid POST 200, plus retryOf and paidUsd 0.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The retry's statusUrl, sent before the render finishes."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}}},"400":{"description":"runId and token are required, or the prompt sent is empty or too long"},"402":{"description":"The wallet balance does not cover this retry; the body carries balanceUsd, priceUsd, and payUrl for a new paid request"},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found, or the ticket is not for a pay-per-call music run"},"409":{"description":"The run is still going, finished successfully, or has already been retried"},"429":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-call music is temporarily unavailable"}}}},"/api/x402/image":{"post":{"operationId":"x402GenerateImage","x-payment-info":{"protocols":["x402"],"price":{"mode":"dynamic","currency":"USD","min":"0.02","max":"0.36"}},"summary":"Generate an image on one of seven models, paid with x402","description":"Generates one image from a prompt on the model you pick, square to 21:9 and up to 4K where the model offers it. Each model sells its own aspect ratios and its own price setting (resolution or quality); GET this path with no parameters for every option and price, and read the amount off the 402 challenge. A setting the model does not honour is refused with a 400 rather than silently dropped. 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). Only the documented fields are read; a request carrying any other field is refused with a 400 before any payment moves.\n\nThe 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. Read status in that body: success carries image, the URL of the image; error means it failed, nothing was charged for it, and retryUrl tries again on the wallet balance the payment left, without paying twice; running means it outlasted the wait (45 seconds by default, Prefer: wait=N for up to 720), so poll statusUrl. Send \"async\": true or Prefer: respond-async for an immediate answer (200, status running) with a statusUrl. 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.","parameters":[{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (default 45, at most 720), before it ends with status running and the statusUrl. Echoed in Preference-Applied."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"additionalProperties":false,"properties":{"prompt":{"type":"string","maxLength":2000,"description":"What the image should show."},"model":{"type":"string","enum":["nano-banana","nano-banana-2","nano-banana-pro","gpt-image-2","seedream-5-pro","flux-2-pro","recraft-v4.1"],"default":"nano-banana","description":"nano-banana (Google Nano Banana (Gemini 2.5 Flash Image)): Fast, cheap and good at following a prompt. The default. nano-banana-2 (Google Nano Banana 2 (Gemini 3.1 Flash Image)): Sharper than Nano Banana, with a resolution choice up to 4K. nano-banana-pro (Google Nano Banana Pro (Gemini 3 Pro Image)): Google's best image model: legible text in the image, complex scenes, up to 4K. gpt-image-2 (OpenAI GPT Image 2): OpenAI image generation with a quality choice; strong at text and layout. seedream-5-pro (ByteDance Seedream 5.0 Pro): Photoreal detail at 2K by default, or 1K for less. flux-2-pro (Black Forest Labs FLUX.2 Pro): Crisp, photographic FLUX output. Square is the cheapest shape. recraft-v4.1 (Recraft V4.1): Design-minded: illustration, brand graphics and clean compositions."},"aspectRatio":{"type":"string","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3","4:5","5:4","21:9"],"default":"1:1","description":"nano-banana: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9. nano-banana-2: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9. nano-banana-pro: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9. gpt-image-2: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 21:9. seedream-5-pro: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9. flux-2-pro: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 21:9. recraft-v4.1: 1:1, 16:9, 9:16, 4:3, 3:4."},"resolution":{"type":"string","enum":["512","1K","2K","4K"],"description":"nano-banana-2: 1K, 512, 2K, 4K (default 1K). nano-banana-pro: 1K, 2K, 4K (default 1K). seedream-5-pro: 2K, 1K (default 2K). Refused on the other models."},"quality":{"type":"string","enum":["low","medium","high"],"description":"gpt-image-2 only: low, medium (default) or high. Refused on the other models."},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll."}}},"example":{"prompt":"a lighthouse on a sea cliff at golden hour, soft mist, cinematic wide shot","model":"nano-banana","aspectRatio":"1:1"}}}},"responses":{"200":{"description":"Payment settled and the response waited for the image. Streamed: the status and headers are sent at once (X-Status-Url carries the statusUrl), then a whitespace byte every 2 seconds, then one complete JSON object. The HTTP status is 200 whatever happened, so read status in the body.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The statusUrl, sent before the image is ready, so a caller whose connection drops can still claim it."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries image. error, partial, and cancelled carry the failure fields. running means it is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"image":{"type":"string","format":"uri","description":"URL of the finished image. Use it exactly as given: its query parameters, including c=, are part of it."},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"alreadyPaid":{"type":"object","description":"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.","properties":{"runId":{"type":"string"},"boughtAt":{"type":"string","format":"date-time"}}},"paidUsd":{"type":"number","description":"What this request cost."},"model":{"type":"string"},"aspectRatio":{"type":"string"},"resolution":{"type":"string"},"quality":{"type":"string"},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"Failed runs only. prompt_refused: the model declined the prompt, so reword it. render_failed: it did not finish; the same prompt may succeed."},"retryable":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to try again on the wallet balance, optionally with {\"prompt\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. The wallet balance the retry spends."}}},"example":{"runId":"2026-09-23T18:04:11.000Z#1a2b3c4d","status":"success","image":"https://www.trezalabs.com/api/media/generated/x402_image/1a2b3c4d.png?s=...&c=...","statusUrl":"https://www.trezalabs.com/api/x402/image?runId=...&token=...","paidUsd":0.06,"model":"nano-banana","aspectRatio":"1:1","transaction":"0x..."}}}},"400":{"description":"Missing or oversized prompt, a model not on the menu, an aspect ratio or setting that model does not offer, or a field this endpoint does not read. Refused before settlement, so nothing was charged."},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions"},"409":{"description":"This payment was already redeemed (code PAYMENT_ALREADY_REDEEMED). Nothing new was generated or charged; send a new payment to buy another."},"429":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-call images is not enabled on this deployment, or is temporarily unavailable."}}},"get":{"operationId":"x402GetImageRun","summary":"Poll a image bought with an x402 payment, or read the menu","description":"Called with no query parameters, returns the menu: what is on sale, every option and price, and how to buy. With runId and token (the statusUrl the paid POST handed back), returns the run's status and, once finished, image: the file URL. The token is the claim ticket and the only credential needed. Send Prefer: wait=N to hold the answer until it finishes, for up to N seconds (at most 720), streamed like the paid POST. A failed run answers with the provider's error, a reason, and a retryUrl.","parameters":[{"name":"runId","in":"query","required":false,"schema":{"type":"string"},"description":"Required to poll a run. Omit both this and token to get the menu."},{"name":"token","in":"query","required":false,"schema":{"type":"string"},"description":"Signed claim ticket from the paid POST. Required to poll a run."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. wait=N holds the answer until the run finishes, for up to N seconds (at most 720)."}],"responses":{"200":{"description":"Run status plus image once it has finished; or, with no query parameters, the menu.","content":{"application/json":{"schema":{"oneOf":[{"title":"Run status","required":["runId","status"],"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries image. error, partial, and cancelled carry the failure fields. running means it is still going; poll statusUrl."},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"image":{"type":"string","format":"uri","description":"URL of the finished image. Use it exactly as given: its query parameters, including c=, are part of it."},"statusUrl":{"type":"string","format":"uri","description":"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":{"type":"integer","description":"Only when status is running: how long to wait before polling statusUrl."},"paidUsd":{"type":"number","description":"What this request cost."},"model":{"type":"string"},"aspectRatio":{"type":"string"},"resolution":{"type":"string"},"quality":{"type":"string"},"network":{"type":"string","description":"Where the payment settled: eip155:8453 (Base) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (Solana)."},"transaction":{"type":"string","description":"The settled payment: a 0x transaction hash on Base, a base58 transaction signature on Solana."},"payer":{"type":"string","description":"The paying wallet."},"error":{"type":"string","description":"Failed runs only. What went wrong, in the provider's words."},"reason":{"type":"string","enum":["prompt_refused","render_failed"],"description":"Failed runs only. prompt_refused: the model declined the prompt, so reword it. render_failed: it did not finish; the same prompt may succeed."},"retryable":{"type":"boolean","description":"Failed runs only. True until the run's one free retry has been used."},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to try again on the wallet balance, optionally with {\"prompt\": \"...\"} in the body. Carries the same signed ticket."},"message":{"type":"string","description":"Failed runs only. What happened and what to do, in one sentence."},"balanceUsd":{"type":"number","description":"Failed runs only. The wallet balance the retry spends."}}},{"type":"object","title":"Menu","description":"Returned when the GET carries no query parameters: what is on sale, the prices, and how to buy.","required":["service","howToBuy","fields"],"properties":{"service":{"type":"string"},"description":{"type":"string"},"howToBuy":{"type":"object","description":"The request to send: method, an example body, and how payment works.","properties":{"method":{"type":"string"},"body":{"type":"object"},"note":{"type":"string"}}},"fields":{"type":"object","description":"Each request field and what it takes.","additionalProperties":{"type":"string"}},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"default":{"type":"boolean"},"note":{"type":"string"},"aspects":{"type":"array","items":{"type":"string"}},"prices":{"type":"array","items":{"type":"object","description":"A price for one option, keyed by the option it varies with.","properties":{"priceUsd":{"type":"number"}}}}}}},"limits":{"type":"object","description":"maxPromptChars."},"delivery":{"type":"string","description":"How the paid call answers and where the file ends up."},"alreadyPaid":{"type":"string","description":"Where a file already paid for can be collected, for a buyer that lost its statusUrl."}}}]}}}},"400":{"description":"One of runId and token was given without the other. The runId contains '#', so a hand-built URL must encode it as %23."},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found"}}}},"/api/x402/image/retry":{"post":{"operationId":"x402RetryImageRun","summary":"Generate a failed image again, without paying twice","description":"A image that failed after the payment settled charged nothing, so the payment is still on the wallet's balance. POST here with the failed run's claim ticket to try again on that balance, with the same settings it was sold with. Send {\"prompt\": \"...\"} to reword it (the fix for prompt_refused), or no body to use the original. Each failed run can be retried once. Waits and streams like the paid POST; \"async\": true or Prefer: respond-async answers at once (200, status running).","parameters":[{"name":"runId","in":"query","required":true,"schema":{"type":"string"},"description":"The failed run, from its status response."},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Signed claim ticket. The retryUrl carries both."},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (default 45, at most 720), before it ends with status running and the statusUrl. Echoed in Preference-Applied."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","maxLength":2000,"description":"A reworded input. Omit to use the original."},"async":{"type":"boolean","default":false,"description":"true answers at once (200, status running) with a statusUrl to poll."}}}}}},"responses":{"200":{"description":"The retry started and the response waited for it, streamed the same way as the paid POST. Same shape as the paid POST 200, plus retryOf and paidUsd 0.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The retry's statusUrl, sent before the render finishes."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}}},"400":{"description":"runId and token are required, or the prompt sent is empty or too long"},"402":{"description":"The wallet balance does not cover this retry; the body carries balanceUsd, priceUsd, and payUrl for a new paid request"},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found, or the ticket is not for a pay-per-call images run"},"409":{"description":"The run is still going, finished successfully, or has already been retried"},"429":{"description":"Rate limit exceeded"},"503":{"description":"Pay-per-call images is temporarily unavailable"}}}},"/api/x402/clip":{"post":{"operationId":"x402ClipVideo","x-payment-info":{"protocols":["x402"],"price":{"mode":"dynamic","currency":"USD","min":"0.04","max":"0.87"}},"summary":"Generate a captioned short from a YouTube video, via x402","description":"Send a YouTube link; get its most shareable 30 to 60 second moment back as a captioned clip reframed on the speaker (9:16 by default, or 4:5, 1:1, 16:9), with a title and caption to post it with. Priced by the source's length: the video is looked up before payment, the 402 quotes the exact price for it ($0.04 for a minute or less, $0.45 for an hour, up to 2 hours), and a video that cannot be clipped (private, age-restricted, live, missing, too long) is refused with a 400 and a code before any payment moves. Only YouTube video links are accepted. Pay via the x402 protocol (HTTP 402), in USDC on Base (eip155:8453) or Solana mainnet; no account, API key, or signup. A clip takes a few minutes: the paid call answers 200 at once with the statusUrl in X-Status-Url, waits about as long as a clip from that length of source usually takes (Prefer: wait=N for up to 720), then ends with status success and video, or status running and the statusUrl to poll. Clip only videos you have the rights to use. 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.","parameters":[{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (at most 720), before it ends with status running and the statusUrl. Without it, the call waits about as long as a clip from that length of source usually takes: about two minutes for a 10-minute video, five for an hour, at most 720 seconds. A client with a fixed timeout, such as the 60 seconds MCP tool calls allow, should send wait=N under it or respond-async."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","description":"A YouTube video link: a watch page, a Short, or a youtu.be link."},"aspectRatio":{"type":"string","enum":["9:16","4:5","1:1","16:9"],"default":"9:16","description":"9:16, 4:5 and 1:1 are cropped on the speaker; 16:9 keeps the original framing."},"async":{"type":"boolean","default":false}}},"example":{"url":"https://www.youtube.com/watch?v=KtTXyBJ2N30","aspectRatio":"9:16"}}}},"responses":{"200":{"description":"Payment settled and the response waited for the clip, streamed (a whitespace byte every 2 seconds, then one JSON object). Read status in the body.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The statusUrl, sent before the render finishes, so a caller whose connection drops can still claim the file."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries video. error, partial, and cancelled carry the failure fields. running means the clip is still rendering; poll statusUrl."},"video":{"type":"string","format":"uri","description":"URL of the finished, captioned clip (MP4). Use it exactly as given: its query parameters, including c=, are part of it."},"outputs":{"type":"object","properties":{"video":{"type":"string","format":"uri"},"moment":{"type":"string","description":"The moment picked, as JSON text: {\"start\", \"end\", \"publish\"}, where publish is a title and a caption to post the clip with."}}},"statusUrl":{"type":"string","format":"uri","description":"The run's status, now and later, carrying its own signed claim ticket. Use it exactly as given: the runId contains '#', encoded as %23."},"pollAfterMs":{"type":"integer"},"alreadyPaid":{"type":"object","description":"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.","properties":{"runId":{"type":"string"},"boughtAt":{"type":"string","format":"date-time"}}},"paidUsd":{"type":"number"},"aspectRatio":{"type":"string"},"sourceMinutes":{"type":"integer","description":"The source length the price was computed from, rounded up."},"source":{"type":"object","description":"What YouTube reported before payment: videoId, title, seconds."},"network":{"type":"string"},"transaction":{"type":"string"},"payer":{"type":"string"},"error":{"type":"string"},"reason":{"type":"string","enum":["prompt_refused","render_failed"]},"retryable":{"type":"boolean"},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to clip again on the wallet balance, optionally with {\"url\": \"...\"} for another YouTube video no longer than the one paid for."},"message":{"type":"string"}}},"example":{"runId":"2026-09-23T18:04:11.000Z#1a2b3c4d","status":"success","video":"https://www.trezalabs.com/api/media/generated/x402_captions/1a2b3c4d.mp4?s=...&c=...","outputs":{"moment":"{\"start\":\"00:12:03,120\",\"end\":\"00:12:51,400\",\"publish\":\"...\"}"},"statusUrl":"https://www.trezalabs.com/api/x402/clip?runId=...&token=...","paidUsd":0.12,"aspectRatio":"9:16","sourceMinutes":12,"transaction":"0x..."}}}},"400":{"description":"Not a single YouTube video link (code SOURCE_URL_INVALID), a video that cannot be clipped (codes SOURCE_UNAVAILABLE, SOURCE_LIVE, SOURCE_TOO_SHORT, SOURCE_TOO_LONG), an aspect ratio not on sale, or a field this endpoint does not read. Refused before settlement, so nothing was charged."},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions, priced for the linked video."},"409":{"description":"This payment was already redeemed (code PAYMENT_ALREADY_REDEEMED)."},"429":{"description":"Rate limit exceeded"},"503":{"description":"Clipping is temporarily unavailable, or YouTube could not be read just now (code SOURCE_LOOKUP_FAILED). Nothing was charged."}}},"get":{"operationId":"x402GetClipRun","summary":"Poll a clip bought with an x402 payment, or read the menu","description":"Called with no query parameters, returns the menu. With runId and token (the statusUrl the paid POST handed back), returns the run's status and, once finished, video and outputs.moment. Send Prefer: wait=N to hold the answer until it finishes, for up to N seconds (at most 720).","parameters":[{"name":"runId","description":"Required to poll a run. Omit both this and token to get the offer list.","in":"query","required":false,"schema":{"type":"string"}},{"name":"token","description":"Signed claim ticket from the paid POST. Required to poll a run.","in":"query","required":false,"schema":{"type":"string"}},{"name":"Prefer","description":"Optional. wait=N holds the answer until the run finishes, for up to N seconds (at most 720), streamed as a whitespace byte every 2 seconds and then the JSON.","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Run status plus video once finished; or, with no query parameters, the menu.","content":{"application/json":{"schema":{"oneOf":[{"title":"Run status","required":["runId","status"],"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries video. error, partial, and cancelled carry the failure fields. running means the clip is still rendering; poll statusUrl."},"video":{"type":"string","format":"uri","description":"URL of the finished, captioned clip (MP4). Use it exactly as given: its query parameters, including c=, are part of it."},"outputs":{"type":"object","properties":{"video":{"type":"string","format":"uri"},"moment":{"type":"string","description":"The moment picked, as JSON text: {\"start\", \"end\", \"publish\"}, where publish is a title and a caption to post the clip with."}}},"statusUrl":{"type":"string","format":"uri","description":"The run's status, now and later, carrying its own signed claim ticket. Use it exactly as given: the runId contains '#', encoded as %23."},"pollAfterMs":{"type":"integer"},"paidUsd":{"type":"number"},"aspectRatio":{"type":"string"},"sourceMinutes":{"type":"integer","description":"The source length the price was computed from, rounded up."},"source":{"type":"object","description":"What YouTube reported before payment: videoId, title, seconds."},"network":{"type":"string"},"transaction":{"type":"string"},"payer":{"type":"string"},"error":{"type":"string"},"reason":{"type":"string","enum":["prompt_refused","render_failed"]},"retryable":{"type":"boolean"},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST here to clip again on the wallet balance, optionally with {\"url\": \"...\"} for another YouTube video no longer than the one paid for."},"message":{"type":"string"}}},{"type":"object","title":"Menu","description":"Returned when the GET carries no query parameters: what is on sale, the prices, and how to buy.","required":["service","howToBuy","fields"],"properties":{"service":{"type":"string"},"description":{"type":"string"},"howToBuy":{"type":"object","description":"The request to send: method, an example body, and how payment works.","properties":{"method":{"type":"string"},"body":{"type":"object"},"note":{"type":"string"}}},"pricing":{"type":"string","description":"How the price is computed."},"fields":{"type":"object","description":"Each request field and what it takes.","additionalProperties":{"type":"string"}},"limits":{"type":"object","description":"sourceSeconds and clipSeconds, each {min, max}."},"delivery":{"type":"string","description":"How the paid call answers and where the file ends up."},"rights":{"type":"string"},"alreadyPaid":{"type":"string","description":"Where a file already paid for can be collected, for a buyer that lost its statusUrl."}}}]}}}},"400":{"description":"One of runId and token was given without the other."},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found"}}}},"/api/x402/clip/retry":{"post":{"operationId":"x402RetryClipRun","summary":"Render a failed clip again, without paying twice","description":"A clip that failed after the payment settled charged nothing for the failed steps, so the payment is on the wallet balance. POST here with the failed run's claim ticket to try again, from the same link or, with {\"url\": \"...\"}, another YouTube video no longer than the one paid for. Each failed run can be retried once.","parameters":[{"name":"runId","description":"The failed run, from its status response.","in":"query","required":true,"schema":{"type":"string"}},{"name":"token","description":"Signed claim ticket from the paid POST. The retryUrl in the status response carries both.","in":"query","required":true,"schema":{"type":"string"}},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl to poll, like \"async\": true. wait=N sets how long the call waits, in seconds (at most 720), before it ends with status running and the statusUrl. Without it, the call waits about as long as a clip from that length of source usually takes: about two minutes for a 10-minute video, five for an hour, at most 720 seconds. A client with a fixed timeout, such as the 60 seconds MCP tool calls allow, should send wait=N under it or respond-async."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"async":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"The retry started and the response waited for it, streamed like the paid POST.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The retry's statusUrl, sent before the render finishes."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}}},"400":{"description":"runId and token are required, or the url sent is not a YouTube video link"},"402":{"description":"The wallet balance does not cover this retry"},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found, or the ticket is not for a clip"},"409":{"description":"The run is still going, finished successfully, or has already been retried"},"429":{"description":"Rate limit exceeded"},"503":{"description":"Clipping is temporarily unavailable"}}}},"/api/x402/short":{"post":{"operationId":"x402NarratedShort","x-payment-info":{"protocols":["x402"],"price":{"mode":"dynamic","currency":"USD","min":"0.87","max":"3.22"}},"summary":"Generate a finished narrated short from a topic, via x402","description":"Send a topic; get a finished 30-second vertical short: a written script, four scenes, an ElevenLabs narrator, a music bed, word-by-word captions and callout cards, plus a title and description to post it with. style motion ($3.22) animates each scene with Google Veo 3.1 Lite; stills ($0.87) edits the scenes' stills with a slow push-in. Pay via the x402 protocol (HTTP 402), in USDC on Base (eip155:8453) or Solana mainnet; no account, API key, or signup. A short takes three to five minutes: the paid call answers 200 at once with the statusUrl in X-Status-Url, waits about as long as that style usually takes (Prefer: wait=N for up to 720), then ends with status success and video, or status running and the statusUrl to poll. If a step fails part way (a scene the video model refuses), the steps that ran are charged, the rest of the payment stays on the wallet balance, and the free retry resumes the run, re-rendering only what failed. 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.","parameters":[{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl; wait=N waits up to N seconds (at most 720) before ending with status running and the statusUrl. Without it, the call waits about as long as that style usually takes: about four and a half minutes for stills, five for motion. A client with a fixed timeout, such as the 60 seconds MCP tool calls allow, should send wait=N under it or respond-async."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topic"],"additionalProperties":false,"properties":{"topic":{"type":"string","maxLength":500,"description":"What the short is about."},"style":{"type":"string","enum":["motion","stills"],"default":"motion"},"voice":{"type":"string","default":"brian","description":"The narrator: a voices[].id from GET /api/x402/short (the /api/x402/speech voices billed at the standard rate, over 30 in all). An unknown voice is refused with a 400 before any payment moves."},"async":{"type":"boolean","default":false}}},"example":{"topic":"how octopuses taste the world with their arms","style":"motion"}}}},"responses":{"200":{"description":"Payment settled and the response waited for the short, streamed (a whitespace byte every 2 seconds, then one JSON object). Read status in the body.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The statusUrl, sent before the render finishes, so a caller whose connection drops can still claim the file."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}},"content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries video. error, partial, and cancelled carry the failure fields. running means the short is still rendering; poll statusUrl."},"video":{"type":"string","format":"uri","description":"URL of the finished short (MP4, 9:16). Use it exactly as given."},"outputs":{"type":"object","properties":{"video":{"type":"string","format":"uri"},"title":{"type":"string"},"description":{"type":"string","description":"Two sentences and hashtags, ready to post."}}},"statusUrl":{"type":"string","format":"uri"},"pollAfterMs":{"type":"integer"},"alreadyPaid":{"type":"object","description":"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.","properties":{"runId":{"type":"string"},"boughtAt":{"type":"string","format":"date-time"}}},"paidUsd":{"type":"number"},"style":{"type":"string"},"voice":{"type":"string"},"network":{"type":"string"},"transaction":{"type":"string"},"payer":{"type":"string"},"error":{"type":"string"},"reason":{"type":"string","enum":["prompt_refused","render_failed"]},"retryable":{"type":"boolean"},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST with no body to finish the short: steps that succeeded are reused, only what failed re-renders. With {\"topic\": \"...\"} it starts over (at the full price, from the balance)."},"message":{"type":"string","description":"Failed runs only. What happened, what the steps that ran were charged, and what to do."}}},"example":{"runId":"2026-09-23T18:04:11.000Z#1a2b3c4d","status":"success","video":"https://www.trezalabs.com/api/media/generated/x402_captions/1a2b3c4d.mp4?s=...&c=...","outputs":{"title":"Octopuses Taste With Their Arms","description":"..."},"statusUrl":"https://www.trezalabs.com/api/x402/short?runId=...&token=...","paidUsd":3.22,"style":"motion","voice":"brian","transaction":"0x..."}}}},"400":{"description":"Missing or oversized topic, a style or voice not on the menu, or a field this endpoint does not read. Refused before settlement, so nothing was charged."},"402":{"description":"Payment required: the challenge response carrying x402 payment instructions, priced for the style asked for."},"409":{"description":"This payment was already redeemed (code PAYMENT_ALREADY_REDEEMED)."},"429":{"description":"Rate limit exceeded"},"503":{"description":"Shorts are temporarily unavailable. Nothing was charged."}}},"get":{"operationId":"x402GetShortRun","summary":"Poll a short bought with an x402 payment, or read the menu","description":"Called with no query parameters, returns the menu. With runId and token (the statusUrl the paid POST handed back), returns the run's status and, once finished, video with outputs.title and outputs.description. Send Prefer: wait=N to hold the answer until it finishes, for up to N seconds (at most 720).","parameters":[{"name":"runId","description":"Required to poll a run. Omit both this and token to get the offer list.","in":"query","required":false,"schema":{"type":"string"}},{"name":"token","description":"Signed claim ticket from the paid POST. Required to poll a run.","in":"query","required":false,"schema":{"type":"string"}},{"name":"Prefer","description":"Optional. wait=N holds the answer until the run finishes, for up to N seconds (at most 720), streamed as a whitespace byte every 2 seconds and then the JSON.","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Run status plus video once finished; or, with no query parameters, the menu.","content":{"application/json":{"schema":{"oneOf":[{"title":"Run status","required":["runId","status"],"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["success","error","partial","cancelled","running"],"example":"success","description":"success carries video. error, partial, and cancelled carry the failure fields. running means the short is still rendering; poll statusUrl."},"video":{"type":"string","format":"uri","description":"URL of the finished short (MP4, 9:16). Use it exactly as given."},"outputs":{"type":"object","properties":{"video":{"type":"string","format":"uri"},"title":{"type":"string"},"description":{"type":"string","description":"Two sentences and hashtags, ready to post."}}},"statusUrl":{"type":"string","format":"uri"},"pollAfterMs":{"type":"integer"},"paidUsd":{"type":"number"},"style":{"type":"string"},"voice":{"type":"string"},"network":{"type":"string"},"transaction":{"type":"string"},"payer":{"type":"string"},"error":{"type":"string"},"reason":{"type":"string","enum":["prompt_refused","render_failed"]},"retryable":{"type":"boolean"},"retryUrl":{"type":"string","format":"uri","description":"Failed runs only. POST with no body to finish the short: steps that succeeded are reused, only what failed re-renders. With {\"topic\": \"...\"} it starts over (at the full price, from the balance)."},"message":{"type":"string","description":"Failed runs only. What happened, what the steps that ran were charged, and what to do."}}},{"type":"object","title":"Menu","description":"Returned when the GET carries no query parameters: what is on sale, the prices, and how to buy.","required":["service","howToBuy","fields"],"properties":{"service":{"type":"string"},"description":{"type":"string"},"howToBuy":{"type":"object","description":"The request to send: method, an example body, and how payment works.","properties":{"method":{"type":"string"},"body":{"type":"object"},"note":{"type":"string"}}},"fields":{"type":"object","description":"Each request field and what it takes.","additionalProperties":{"type":"string"}},"styles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"default":{"type":"boolean"},"priceUsd":{"type":"number"}}}},"limits":{"type":"object","description":"maxTopicChars, durationSeconds {min, max}, and aspectRatio."},"delivery":{"type":"string","description":"How the paid call answers and where the file ends up."},"alreadyPaid":{"type":"string","description":"Where a file already paid for can be collected, for a buyer that lost its statusUrl."}}}]}}}},"400":{"description":"One of runId and token was given without the other."},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found"}}}},"/api/x402/short/retry":{"post":{"operationId":"x402RetryShortRun","summary":"Render a failed short again, without paying twice","description":"POST with the failed run's claim ticket. With no body the run resumes: steps that succeeded are reused and only what failed re-renders (a writer whose script a later step could not use is re-run, with everything after it), and the wallet balance must cover what will re-run, never more than the full price. With {\"topic\": \"...\"} it starts over, at the full price, from the wallet balance. Each failed run can be retried once.","parameters":[{"name":"runId","description":"The failed run, from its status response.","in":"query","required":true,"schema":{"type":"string"}},{"name":"token","description":"Signed claim ticket from the paid POST. The retryUrl in the status response carries both.","in":"query","required":true,"schema":{"type":"string"}},{"name":"Prefer","in":"header","required":false,"schema":{"type":"string"},"description":"Optional. respond-async answers at once (200, status running) with a statusUrl; wait=N waits up to N seconds (at most 720) before ending with status running and the statusUrl. Without it, the call waits about as long as that style usually takes: about four and a half minutes for stills, five for motion. A client with a fixed timeout, such as the 60 seconds MCP tool calls allow, should send wait=N under it or respond-async."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"topic":{"type":"string","maxLength":500},"async":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"The retry started and the response waited for it, streamed like the paid POST.","headers":{"X-Status-Url":{"schema":{"type":"string","format":"uri"},"description":"The retry's statusUrl, sent before the render finishes."},"Location":{"schema":{"type":"string","format":"uri"},"description":"Only on the immediate answer (\"async\": true or Prefer: respond-async): the statusUrl again, where HTTP clients look for where a result will be."},"Retry-After":{"schema":{"type":"integer"},"description":"Only on the immediate answer: seconds to wait before polling the statusUrl."},"Preference-Applied":{"schema":{"type":"string"},"description":"The Prefer value honoured, e.g. wait=120 or respond-async."}}},"400":{"description":"runId and token are required, or the topic sent is empty or too long"},"402":{"description":"The wallet balance does not cover this retry"},"403":{"description":"Invalid, expired, or mismatched token"},"404":{"description":"Run not found, or the ticket is not for a short"},"409":{"description":"The run is still going, finished successfully, or has already been retried"},"429":{"description":"Rate limit exceeded"},"503":{"description":"Shorts are temporarily unavailable, or the retry could not be queued; nothing was charged and the retry is still available"}}}},"/api/x402/purchases":{"get":{"operationId":"listX402Purchases","summary":"List the purchases a wallet paid for","description":"Free. Everything the signing wallet bought from the pay-per-call endpoints in the last 30 days, newest first, for a buyer that lost its statusUrl. Called without a SIGN-IN-WITH-X header it answers 402 with a sign-in challenge and nothing to pay (accepts is empty; the challenge is the x402 sign-in-with-x extension, in the PAYMENT-REQUIRED header and the body). Sign it with the wallet that paid, a SIWE (EIP-4361) message on Base (eip155:8453, smart-contract wallets included) or a SIWS message on Solana, and send it back base64-encoded in the SIGN-IN-WITH-X header within five minutes; each challenge works once. agentcash signs it on its own; with @x402/fetch, register createSIWxClientExtension from @x402/extensions/sign-in-with-x. Each purchase comes with its file once finished, a fresh statusUrl, and a retryUrl when a render failed.","tags":["x402"],"security":[{"WalletAuth":[]}],"parameters":[{"name":"SIGN-IN-WITH-X","in":"header","required":false,"schema":{"type":"string"},"description":"Base64 of the signed Sign-In-With-X payload: the challenge's fields plus address, chainId, type, and signature. Without it the call answers 402 with a challenge."}],"responses":{"200":{"description":"The signing wallet's purchases.","content":{"application/json":{"schema":{"type":"object","properties":{"wallet":{"type":"string","description":"The address the proof was signed by."},"chainId":{"type":"string","description":"eip155:8453 or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp."},"purchases":{"type":"array","items":{"type":"object","properties":{"product":{"type":"string","enum":["video","speech","music","image","clip","short"]},"item":{"type":"string","description":"What was bought, e.g. video, 5s 16:9 seedance-2.5 from an image."},"runId":{"type":"string"},"status":{"type":"string","enum":["success","running","error","partial","cancelled"]},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":"string","format":"date-time"},"url":{"type":"string","format":"uri","description":"The file, once finished. Also under the product's own field (video, audio, image). Use it exactly as given."},"statusUrl":{"type":"string","format":"uri","description":"A fresh claim ticket for this purchase, good for 24 hours: GET it for the status, or the file."},"retryUrl":{"type":"string","format":"uri","description":"A failed render not yet retried: POST here to render it again without paying."},"retriedByRunId":{"type":"string"},"collected":{"type":"boolean","description":"Whether the file had reached the buyer before this list."}}}},"message":{"type":"string"}}}}}},"401":{"description":"The proof did not check out (another origin, stale, used before, or a signature that does not match the address). The body has code and error, and a fresh challenge rides along."},"402":{"description":"No SIGN-IN-WITH-X header: the sign-in challenge. accepts is empty, so there is nothing to pay; sign extensions[\"sign-in-with-x\"] and ask again."},"429":{"description":"Too many signed requests from one source."}}}},"/api/account/balance":{"get":{"operationId":"getAccountBalance","summary":"Get credit balance and plan usage","description":"Current prepaid credit balance, plan usage, and purchasable credit packs. Accepts a scoped API key (any pipelines scope), an MCP OAuth access token, or a session bearer. Use it to budget runs before starting them; when the balance is low, hand topUpUrl to a human, since credits are purchased by a signed-in human.","responses":{"200":{"description":"Balance summary","content":{"application/json":{"schema":{"type":"object","properties":{"balanceUsd":{"type":"number"},"enforced":{"type":"boolean","description":"False when this account runs free (credits not enforced)."},"markup":{"type":"number","description":"Multiplier applied to estimated provider cost when charging the balance."},"typicalVideoChargeUsd":{"type":"number"},"approxVideosRemaining":{"type":"integer"},"plan":{"type":"object","nullable":true,"properties":{"name":{"type":"string"},"status":{"type":"string"},"requestsThisPeriod":{"type":"integer"},"includedRequests":{"type":"integer"},"overLimit":{"type":"boolean"}}},"packs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"priceUsd":{"type":"number"},"creditsUsd":{"type":"number"},"label":{"type":"string"}}}},"topUpUrl":{"type":"string","format":"uri"},"x402":{"type":"object","description":"Present when the deployment supports agent-native x402 top-ups.","properties":{"url":{"type":"string","format":"uri"},"topUpPerCallUsd":{"type":"number"},"network":{"type":"string","description":"The first network the x402 endpoints accept: eip155:8453 (Base)."},"networks":{"type":"array","items":{"type":"string"},"description":"Every network a payment may arrive on, Base first: eip155:8453 and solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp. A Base wallet and a Solana wallet are separate accounts."},"video":{"type":"object","description":"Present when pay-per-video is on: POST /api/x402/video buys one clip for one payment, with no account. sizes lists the default model's clip lengths; GET that URL for every model and price.","properties":{"url":{"type":"string","format":"uri"},"fromUsd":{"type":"number"},"sizes":{"type":"array","items":{"type":"object","properties":{"seconds":{"type":"number"},"priceUsd":{"type":"number"}}}}}}}}}}}}},"401":{"description":"Missing or invalid bearer token"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/tools/list_pipelines":{"post":{"operationId":"list_pipelines","summary":"List pipelines","description":"List the video pipelines on your Treza account, with status and a summary of recent runs.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/get_pipeline":{"post":{"operationId":"get_pipeline","summary":"Get pipeline","description":"Get one pipeline: metadata plus a summary of its node graph (node ids, types, labels). Pass includeGraph to get the complete graph — every node with position and config, and every edge — which is what update_pipeline needs as a starting point. Never write a graph reconstructed from the summary: it drops configs.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1},"includeGraph":{"description":"Return the full nodes/edges graph instead of the summary. Required reading before an update_pipeline.","type":"boolean"}},"required":["pipelineId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/list_runs":{"post":{"operationId":"list_runs","summary":"List runs","description":"List recent runs for a pipeline, newest first.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"maximum":50}},"required":["pipelineId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/get_run":{"post":{"operationId":"get_run","summary":"Get run","description":"Get one run: overall status plus per-node results and output URLs. Poll this after run_pipeline, assemble_video or edit_asset until the status is no longer \"running\". A finished run carries `outputs`, what its Output nodes returned (for assemble_video and edit_asset, the finished file). While a run is still \"running\", `nodes` lists the steps that have completed so far (with their outputs) and `finishedAt`/`durationMs` are omitted.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1},"runId":{"type":"string","minLength":1}},"required":["pipelineId","runId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/run_pipeline":{"post":{"operationId":"run_pipeline","summary":"Run pipeline","description":"Start a pipeline run in the background and return its runId immediately. Video pipelines take minutes; poll get_run for progress. Runs consume account credits. `inputs` is keyed by entry node id (get_pipeline lists them as entryNodes): a Text Input node takes text, an HTTP Payload node takes a JSON value, and a File node takes a URL that overrides the file placed on the canvas, so a clipping pipeline can be pointed at a new source video per run without editing the graph. An entry node left out runs with the value saved on the canvas; one with nothing saved is refused, so pass it (or \"\" to leave it blank on purpose), and ask the user what it should say when the request does not tell you. A key that names no entry node is refused before anything runs or is charged. Before running a pipeline you built or changed in this conversation, call estimate_run_cost and wait for the person to accept the price. Every run renders every node again, so to add music, narration or captions to a video an earlier run made, pass its url from get_run outputs to assemble_video (musicPrompt composes a bed) or edit_asset rather than editing the pipeline and running it again.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1},"inputs":{"type":"object","additionalProperties":{}},"preview":{"description":"Render a cheap first look instead of the whole run: the first video step and the steps that feed it, as one Veo 3.1 Lite clip of up to 8 seconds at 720p. estimate_run_cost and a refused run quote it as `preview` when the balance does not cover the run. Only on the person's yes. Returns the pipelineId get_run needs, which is not this pipeline's.","type":"boolean"}},"required":["pipelineId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/list_assets":{"post":{"operationId":"list_assets","summary":"List assets","description":"Browse the account's media library: every image, video, and audio file its pipeline runs and chat generations produced, newest first. The urls it returns are the ones assemble_video and edit_asset accept, so start here when asked to cut, caption, score, or otherwise finish media the account already has. A file the user attached in the conversation is not here until import_media adds it. Clients that show panels let the person pick files from this list: the picks reach you as context naming each file by its #position in the list with its exact url, and the person then says in the chat what to do with them. Apply that to exactly those files, and do not assume picks mean joining them.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"description":"Only return assets of this kind.","type":"string","enum":["image","video","audio"]},"query":{"description":"Case-insensitive text matched against each asset's generation prompt, e.g. \"horse\". Omit for the newest regardless of subject.","type":"string","maxLength":200},"limit":{"description":"How many to return, newest first. Default 12.","type":"integer","minimum":1,"maximum":50}}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/import_media":{"post":{"operationId":"import_media","summary":"Import media","description":"Add an image, video or audio file to the account's media library, so assemble_video and edit_asset can use it: a file the user attached in this conversation (file, where the client passes attachments), a public https link to the file itself (url), or a local file uploaded through create_upload_url (url set to its mediaUrl). Returns the library url. Putting an attached logo in the corner of a video or a picture is this, then edit_asset on the video or picture with an overlay operation whose inputs.image is the returned url and whose config.position names the corner (a picture comes back a PNG). Images up to 25 MB, audio 100 MB, video 200 MB; the type is read from the file, and anything else (SVG, PDF, a web page) is refused. When the user attached a file you cannot pass on (you see it but have no file or link to give), ask them to drop it into their Treza library at https://www.trezalabs.com/chat/assets and find it with list_assets. Needs the pipelines:write scope. Spends no credits.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"description":"A file the user attached in this conversation.","type":"object","properties":{"download_url":{"type":"string","description":"Where the uploaded file can be downloaded."},"file_id":{"type":"string","description":"The id of the uploaded file."},"mime_type":{"description":"Its type as uploaded.","type":"string"},"file_name":{"description":"Its name as uploaded.","type":"string"}},"required":["download_url","file_id"]},"url":{"description":"A public https link to an image, video or audio file, when there is no attachment. Pass file or url, not both.","type":"string"},"name":{"description":"What to call it in the library, e.g. \"Logo\"; list_assets query matches it. Defaults to the file name.","type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/create_upload_url":{"post":{"operationId":"create_upload_url","summary":"Create upload URL","description":"A one-time link to upload a local file into the media library, for a client that can run a command (Claude Code, Codex, Cursor): PUT the file to uploadUrl with the returned Content-Type (the result includes the curl command), then call import_media with url set to the returned mediaUrl. The link expires in 15 minutes and signs one content type. If you cannot run a command, do not call this: ask the person to drop the file into their Treza library in the web app and find it with list_assets. Needs the pipelines:write scope. Spends no credits.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contentType":{"type":"string","enum":["image/png","image/jpeg","image/webp","image/gif","video/mp4","video/quicktime","video/webm","audio/mpeg","audio/mp4","audio/wav","audio/ogg"],"description":"The type of the file you will upload, e.g. \"image/png\" for a PNG logo. The PUT must send exactly this Content-Type."}},"required":["contentType"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/assemble_video":{"post":{"operationId":"assemble_video","summary":"Assemble video","description":"Join clips the account already has into ONE finished video, in the order given, optionally over a voiceover (narrationUrl) and a music bed, which become separate tracks with the clips ducked under the voice, and with burned-in captions. The bed is a track from the library (musicUrl) or one composed for this cut from a description (musicPrompt), never both. One clip plus a track is how to put music or narration under a single video: \"add music to my video\" is clipUrls [that video] with a musicPrompt, and no pipeline to build. Every url must come from list_assets or a run's outputs in get_run. Stills hold 3 seconds each, or stretch so a longer voiceover plays in full; lengthSec in the result is how long the cut will run, and any warning in it should be passed on. Renders in the background: poll get_run with the returned pipelineId and runId. Free once the account has bought credits (the result carries charged: false), so never ask a paying account to top up before assembling; charged on a trial. Needs the pipelines:run and pipelines:write scopes.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clipUrls":{"minItems":1,"maxItems":12,"type":"array","items":{"type":"string","minLength":1},"description":"Video or image urls in play order: two or more, or exactly one when narrationUrl, musicUrl or musicPrompt is set."},"narrationUrl":{"description":"Voiceover laid over the whole edit. Everything else ducks under it.","type":"string"},"musicUrl":{"description":"Music bed under the whole edit, beneath the clips' own sound and any narration: an audio url from list_assets. Leave it out when passing musicPrompt.","type":"string"},"musicPrompt":{"description":"Compose the music bed for this cut instead of taking one from the library: genre, mood, tempo and instruments, e.g. \"calm cinematic ambient, soft piano, gentle pads, no vocals\". It sits and ducks exactly like a musicUrl bed and takes the same fades. Written by the Audio Generation node's default model (get_node_type music-gen), or its full-song model when the cut runs past 30 seconds; the result names the model as composedMusic. Leave it out when passing musicUrl.","type":"string","maxLength":1000},"audioFadeInSec":{"description":"Fade the music bed (or the only track) up over this many seconds at the start. Applies to a composed bed too.","type":"number","minimum":0,"maximum":30},"audioFadeOutSec":{"description":"Fade the music bed (or the only track) down over this many seconds at the end, with the picture left as it is; 2 to 4 reads as a deliberate ending. Narration is never faded when there is a bed. For the whole ending to fade, use fadeOutSec.","type":"number","minimum":0,"maximum":30},"fadeInSec":{"description":"Fade the opening up from black over this many seconds: the first shot's picture and the music bed together.","type":"number","minimum":0,"maximum":30},"fadeOutSec":{"description":"Fade the whole ending to black and silence over this many seconds: the last shot's picture eases down and the music bed fades with it. This is \"fade out at the end\"; 1.5 to 3 reads as a deliberate ending. The transition argument is the cut between shots and does not touch the ending.","type":"number","minimum":0,"maximum":30},"captions":{"description":"Transcribe the finished edit and burn captions in. Only worth it when the clips or narration contain speech.","type":"boolean"},"orientation":{"description":"\"vertical\" is 1080x1920 for Shorts, Reels and TikTok, \"horizontal\" is 1920x1080, \"match_first\" (the default) keeps the first clip's shape. Shots that do not fill the frame are letterboxed, never cropped.","type":"string","enum":["vertical","horizontal","match_first"]},"transition":{"description":"How each shot hands off to the next: the join between two shots, applied at every cut. Defaults to a hard cut. \"fadeblack\" dips to black between shots; it is not a fade-out at the end, which is fadeOutSec.","type":"string","enum":["none","fade","fadeblack","wipeleft","zoomin"]},"name":{"description":"What to call the render, e.g. \"Beach trip cut\".","type":"string","maxLength":200}},"required":["clipUrls"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/edit_asset":{"post":{"operationId":"edit_asset","summary":"Edit asset","description":"Run pipeline nodes over ONE video, image, or audio file the account already has and get the edited file back: captions, an upscale, background removal, lipsync, a crop, an overlay, a cutaway, a thumbnail, a trim, or any other transform in list_node_types. To put music under a video, use assemble_video with musicPrompt instead. Operations run in order, each fed the output of the one before, so \"caption it and upscale it\" is one call with two operations; a Transcribe stage is added wherever an operation needs a transcript. Check a node's config and ports with get_node_type first. A setting the node does not define, or a value outside what it offers, is refused with the valid ones listed. The source, and any url given as an input, must come from list_assets or a run's outputs in get_run. Renders in the background and spends credits: poll get_run with the returned pipelineId and runId. Needs the pipelines:run and pipelines:write scopes.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sourceUrl":{"type":"string","minLength":1,"description":"The asset to edit, from list_assets or a run output."},"operations":{"minItems":1,"maxItems":6,"type":"array","items":{"type":"object","properties":{"nodeType":{"type":"string","minLength":1,"description":"A node type from list_node_types, spelled exactly, e.g. \"captions\", \"video-upscale\", \"extract-clip\"."},"config":{"description":"The node's config as get_node_type describes it. Omitted fields take the node's defaults.","type":"object","additionalProperties":{}},"inputs":{"description":"Extra input on a named port: an asset url, e.g. {\"audio\": \"<url>\"} for lipsync, or, for a port that takes words, the text itself, e.g. {\"transcript\": \"Seven metres of wing.\"}. The asset being edited is wired for you.","type":"object","additionalProperties":{"type":"string"}}},"required":["nodeType"]},"description":"The nodes to run over the asset, in order."},"name":{"description":"What to call the render, e.g. \"Captioned short\".","type":"string","maxLength":200}},"required":["sourceUrl","operations"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/list_node_types":{"post":{"operationId":"list_node_types","summary":"List node types","description":"List the node types available for building pipeline graphs, grouped by category. Use get_node_type for a full port/config schema before wiring a node.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"category":{"description":"Filter to one category id (e.g. \"ai\", \"input\", \"output\").","type":"string"}}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/get_node_type":{"post":{"operationId":"get_node_type","summary":"Get node type","description":"Full definition of one node type: input/output ports and the config field schema needed to author a node of this type. For a model-bearing node the live model catalog comes with it, and for video-gen each model carries what a second of video costs (usdPerSecond), so compare prices before choosing one.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","minLength":1}},"required":["type"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/list_pipeline_templates":{"post":{"operationId":"list_pipeline_templates","summary":"List pipeline templates","description":"Ready-made pipelines that create_pipeline can copy with templateId, so you do not have to draw a graph node by node. Each entry lists its entry nodes, the ids to key run_pipeline `inputs` by, and estimatedChargeUsd, what one run charges the balance as the template stands, to compare against get_credit_balance before you pick one. Clipping a long recording, file, or direct video URL into a captioned vertical clip is \"video-clip-factory\" (entry node \"source\", a File node that takes a URL); \"podcast-clip-factory\" pulls the newest episode from a podcast RSS feed; \"campaign-clip\" is for Vyro / Whop clipping briefs.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/create_pipeline":{"post":{"operationId":"create_pipeline","summary":"Create pipeline","description":"Create a new pipeline (as a draft) on your Treza account, from a template (templateId, see list_pipeline_templates) or with an initial node graph. Use list_node_types / get_node_type to learn the graph vocabulary: every edge names the two nodes it joins and a port on each, e.g. {\"source\":\"music\",\"sourceHandle\":\"audio\",\"target\":\"sequence\",\"targetHandle\":\"shots\"}. A graph with error-level issues is refused with every issue listed, and nothing is saved. That includes a config key the node does not define, a value outside a field's options or range, and a model id, duration, aspect ratio or voice the chosen model does not offer: pick them from get_node_type. Drafts can be run with run_pipeline; publish_pipeline makes them invokable over the public API. To put music under a video the account already has, call assemble_video with musicPrompt instead of building a graph.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"templateId":{"description":"Copy this template's graph (ids from list_pipeline_templates). Ignored when nodes/edges are supplied.","type":"string"},"nodes":{"description":"The graph's nodes. Pass together with edges.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Unique id. Edges, and run_pipeline inputs, refer to the node by it."},"type":{"type":"string","minLength":1,"description":"A node type from list_node_types, e.g. \"file\", \"music-gen\", \"sequence\", \"output\"."},"label":{"description":"Name shown on the canvas.","type":"string"},"position":{"description":"Canvas position. Laid out automatically when omitted.","type":"object","properties":{"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y"]},"config":{"description":"Settings, as get_node_type's configSchema describes them. A node this call adds starts from its type's defaults, and what you pass overrides them.","type":"object","additionalProperties":{}}},"required":["id","type"],"additionalProperties":{},"description":"One node: an id, a type from list_node_types, and its config."}},"edges":{"description":"Wires between node ports, e.g. {\"source\":\"music\",\"sourceHandle\":\"audio\",\"target\":\"sequence\",\"targetHandle\":\"shots\"}. Pass together with nodes; [] for none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Unique id for this wire. Assigned when omitted.","type":"string","minLength":1},"source":{"type":"string","minLength":1,"description":"Id of the node the wire leaves, as given in `nodes`."},"sourceHandle":{"type":"string","minLength":1,"description":"Output port on the source node: an `outputs` id from get_node_type, e.g. \"audio\" on music-gen or \"video\" on sequence. \"out\" is the generic output the canvas draws; name the port instead on a node with more than one output besides \"result\" (update_pipeline refuses a new generic wire there)."},"target":{"type":"string","minLength":1,"description":"Id of the node the wire enters, as given in `nodes`."},"targetHandle":{"type":"string","minLength":1,"description":"Input port on the target node: an `inputs` id from get_node_type, e.g. \"shots\" on sequence or \"prompt\" on music-gen. \"in\" is the generic input the canvas draws, which guesses a port by media type at run time; name the port instead on a node with more than one input (update_pipeline refuses a new generic wire there)."}},"required":["source","sourceHandle","target","targetHandle"],"additionalProperties":false,"description":"A wire from one node's output port to another node's input port. An edge is {\"source\": node id, \"sourceHandle\": output port id, \"target\": node id, \"targetHandle\": input port id, \"id\": optional unique id}, e.g. {\"source\":\"music\",\"sourceHandle\":\"audio\",\"target\":\"sequence\",\"targetHandle\":\"shots\"}. Port ids are the ones get_node_type lists under outputs and inputs."}}},"required":["name"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/update_pipeline":{"post":{"operationId":"update_pipeline","summary":"Update pipeline","description":"Update a pipeline: rename it, change its description, or replace its node graph. For a graph change, read the current one with get_pipeline includeGraph and send the whole graph back, nodes and edges together, every edge naming the two nodes it joins and a port on each, e.g. {\"source\":\"music\",\"sourceHandle\":\"audio\",\"target\":\"sequence\",\"targetHandle\":\"shots\"}. A graph with error-level issues is refused with every issue listed, and nothing is saved; settings you change are checked against get_node_type and the live model catalog the same way create_pipeline checks them. To add music, narration or captions to a video a run already made, pass its url from get_run outputs to assemble_video or edit_asset instead: a changed pipeline renders every node again when it runs, the video included, and charges for it.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"nodes":{"description":"The complete new node list. Pass together with edges; the graph is replaced whole.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Unique id. Edges, and run_pipeline inputs, refer to the node by it."},"type":{"type":"string","minLength":1,"description":"A node type from list_node_types, e.g. \"file\", \"music-gen\", \"sequence\", \"output\"."},"label":{"description":"Name shown on the canvas.","type":"string"},"position":{"description":"Canvas position. Laid out automatically when omitted.","type":"object","properties":{"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y"]},"config":{"description":"Settings, as get_node_type's configSchema describes them. A node this call adds starts from its type's defaults, and what you pass overrides them.","type":"object","additionalProperties":{}}},"required":["id","type"],"additionalProperties":{},"description":"One node: an id, a type from list_node_types, and its config."}},"edges":{"description":"The complete new wire list, e.g. {\"source\":\"music\",\"sourceHandle\":\"audio\",\"target\":\"sequence\",\"targetHandle\":\"shots\"}. Pass together with nodes; [] for none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Unique id for this wire. Assigned when omitted.","type":"string","minLength":1},"source":{"type":"string","minLength":1,"description":"Id of the node the wire leaves, as given in `nodes`."},"sourceHandle":{"type":"string","minLength":1,"description":"Output port on the source node: an `outputs` id from get_node_type, e.g. \"audio\" on music-gen or \"video\" on sequence. \"out\" is the generic output the canvas draws; name the port instead on a node with more than one output besides \"result\" (update_pipeline refuses a new generic wire there)."},"target":{"type":"string","minLength":1,"description":"Id of the node the wire enters, as given in `nodes`."},"targetHandle":{"type":"string","minLength":1,"description":"Input port on the target node: an `inputs` id from get_node_type, e.g. \"shots\" on sequence or \"prompt\" on music-gen. \"in\" is the generic input the canvas draws, which guesses a port by media type at run time; name the port instead on a node with more than one input (update_pipeline refuses a new generic wire there)."}},"required":["source","sourceHandle","target","targetHandle"],"additionalProperties":false,"description":"A wire from one node's output port to another node's input port. An edge is {\"source\": node id, \"sourceHandle\": output port id, \"target\": node id, \"targetHandle\": input port id, \"id\": optional unique id}, e.g. {\"source\":\"music\",\"sourceHandle\":\"audio\",\"target\":\"sequence\",\"targetHandle\":\"shots\"}. Port ids are the ones get_node_type lists under outputs and inputs."}}},"required":["pipelineId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/publish_pipeline":{"post":{"operationId":"publish_pipeline","summary":"Publish pipeline","description":"Publish a pipeline's current draft graph as an immutable deployed version. Publishing requires a fully valid graph (no errors or warnings) and makes the pipeline invokable via the public /invoke and /chat/completions APIs.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1}},"required":["pipelineId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/set_schedule_paused":{"post":{"operationId":"set_schedule_paused","summary":"Pause or resume schedule","description":"Pause or resume a published pipeline's schedule. Pausing flips the flag the cron runner checks without touching the published graph, so the deployed snapshot stays an honest record of what was published. Resuming re-anchors the schedule to now, so it waits for the next real slot instead of replaying slots missed while paused. Requires the pipelines:write scope. Schedules themselves are authored as a schedule-trigger node (see get_node_type) and armed by publish_pipeline.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1},"paused":{"type":"boolean","description":"true pauses the schedule; false resumes it."}},"required":["pipelineId","paused"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}}}},"/api/v1/tools/list_connected_channels":{"post":{"operationId":"list_connected_channels","summary":"List connected channels","description":"The YouTube channels and TikTok accounts connected to this Treza account, with the ids a publishing node needs. A youtube-upload node needs a channelId and a tiktok-upload node needs an openId; both come from here. To connect one, call connect_channel and give the person its link.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/publish_asset":{"post":{"operationId":"publish_asset","summary":"Publish asset","description":"Prepare a post of one finished video to a connected YouTube channel or TikTok account, for the person to review and confirm. It posts nothing itself: clients that show panels put the video, the account and the post's settings in front of the person, and it goes out only when they press Post there (TikTok requires them to choose who can view it and to confirm its disclosures themselves). Call it only when the person asked to publish. The video must come from list_assets or a finished run's outputs in get_run, copied exactly; write a real title (on TikTok it is the caption, hashtags allowed). channelId picks the channel or TikTok account from list_connected_channels when more than one is connected; with none connected, call connect_channel first. A client without panels cannot post from here: build a pipeline that ends in a youtube-upload or tiktok-upload node instead. Needs the pipelines:write scope.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"videoUrl":{"type":"string","minLength":1,"description":"The video to post, from list_assets or a run output."},"platform":{"type":"string","enum":["youtube","tiktok"],"description":"Where to post."},"channelId":{"description":"A YouTube channelId or TikTok openId from list_connected_channels. Omit when only one is connected.","type":"string"},"title":{"type":"string","minLength":1,"maxLength":2200,"description":"The title (YouTube, up to 100 characters) or the caption (TikTok)."},"description":{"description":"YouTube description. Ignored on TikTok.","type":"string","maxLength":5000},"tags":{"description":"YouTube tags. Ignored on TikTok.","maxItems":30,"type":"array","items":{"type":"string"}},"visibility":{"description":"YouTube visibility to suggest; the person can change it before posting. On TikTok the person chooses.","type":"string","enum":["public","unlisted","private"]}},"required":["videoUrl","platform","title"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/connect_channel":{"post":{"operationId":"connect_channel","summary":"Connect channel","description":"A link the person opens to connect a YouTube channel or a TikTok account to their Treza account, so a pipeline can post there. Returns what is already connected on that platform and connectUrl. Only the person can finish it, in their own browser: give them the link, tell them the platform will ask their permission to post and that the page says when it is connected, and after they say so check list_connected_channels. The link asks them to sign in to Treza first with the Google account this connection uses, and it does not expire. When something is already connected, the link adds another, so only offer it when that is what they want.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","enum":["youtube","tiktok"],"description":"Which account to connect."}},"required":["platform"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/get_credit_balance":{"post":{"operationId":"get_credit_balance","summary":"Get credit balance","description":"Current prepaid credit balance, plan usage, and purchasable credit packs for this account. Use it to budget before starting runs. When the balance is low, hand the returned topUpUrl to a signed-in human; if the response carries an x402 block, a wallet-holding agent can pay that endpoint to top itself up with no human involved. That block also carries x402.video when this deployment sells single clips: one payment renders and returns one video without touching the balance, which is the cheaper path for a one-off.","security":[{"bearerAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}},"/api/v1/tools/estimate_run_cost":{"post":{"operationId":"estimate_run_cost","summary":"Estimate run cost","description":"Estimate what one run of a pipeline will charge the credit balance, which steps the money goes to (`lines`), and whether the current balance covers it. Estimates use the draft graph, the same one run_pipeline executes. When a video step would render the same clip for far less on Veo 3.1 Lite, `cheaper` names it with the run's price on it. When the balance covers neither, `preview` prices a first look it does cover: the first video step alone as a short Veo 3.1 Lite clip, which run_pipeline with preview: true renders. Call this before running a pipeline you built or changed in this conversation, tell the person the price (and the preview's, when there is one), and wait for their go-ahead before run_pipeline. Clients that show panels put the estimate in front of them with a Start render button, which sends that go-ahead as their message.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","minLength":1}},"required":["pipelineId"]}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The arguments were refused. `error` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"401":{"description":"No API key, or one that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"402":{"description":"The credit balance does not cover this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"403":{"description":"The API key lacks a permission this tool needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"404":{"description":"No such tool, or the pipeline, run or asset is not on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}},"429":{"description":"Too many runs started or in progress. Wait, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolError"}}}}},"x-read-only":true}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Scoped API key created in the Treza platform (treza_live_...)"},"WalletAuth":{"type":"apiKey","in":"header","name":"SIGN-IN-WITH-X","x-agentcash-auth-kind":"siwx","description":"Sign-In-With-X (the x402 sign-in-with-x extension, CAIP-122): a wallet's signed proof of itself, for the free purchases list. Answer the 402 challenge; no payment."}},"schemas":{"ToolError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"What went wrong, written to be acted on."},"code":{"type":"string","description":"A stable code for some refusals, e.g. INSUFFICIENT_CREDITS, INVALID_GRAPH, UNKNOWN_VIDEO, UNKNOWN_TOOL."},"note":{"type":"string","description":"What to do next, when the server knows."}},"additionalProperties":true},"RunAccepted":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","example":"running"}}},"RunStatus":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","description":"running, succeeded, failed, or cancelled"},"outputs":{"type":"object","description":"Values keyed by the pipeline's output contract, present once the run succeeds. Media outputs are returned as URLs.","additionalProperties":true},"error":{"type":"string"}}}}}}