# Variant Video API Generate short-form finance videos over HTTPS. Submit a template and optionally a ticker; the render farm writes the script, voices it, animates a presenter and composites a 9:16 clip (~30s). You get a CDN URL back by webhook or by polling. Renders take ~15-25 minutes (trade-video: ~3-5). Base URL: https://variantvideo.com Keys: minted at https://variantvideo.com/keys (shown once). Send as `Authorization: Bearer vv_...` on every request. ## Endpoints ### POST /api/v1/videos — create a render Body (JSON): - template (required): "earnings-call" | "recent-event" | "catalyst" | "catalyst-split" | "social-clip" | "chart-head" | "talking-head" | "talking-head-split" | "trade-video" | "showdown-no-lipsync" - ticker (optional): e.g. "NKE". Required for catalyst, catalyst-split and chart-head. Required for social-clip too, unless tweet_url is given — the post then carries the clip and no chart is drawn. Optional for earnings-call and recent-event, which pick the newest thing worth covering when omitted. - voice (optional): a voice id from the list below; default "af_sarah". - photo_url: https URL of a face photo (JPEG/PNG/WebP) for the presenter. REQUIRED unless variant.api_key is given, in which case the Variant avatar's own face (and its handle on the identity bar) is used. A render is never given a default face. - tweet_url (optional, social-clip only): an X post URL rendered as a card on the clip, and the narration is written as a reaction to that post. With a ticker the card sits above the chart; without one the card takes the chart's place (no chart, no ticker chip). - is_general (optional): true renders a general video with no identity bar (no handle, channel, tier, or ticker chip in the frame). MAKING A GENERAL CLIP FOR A VARIANT CHANNEL? Set variant.as_channel: true (+ channel_id) -- that one flag does both halves: Variant plays the post faceless AND the video renders with is_general on automatically. Use is_general alone only for a clean video you are not publishing. - notes (optional): your own material; the model writes the script and title from it instead of the feeds. script (optional): spoken verbatim, skips the model. avatar_mode: "realistic"|"cartoon" for the Variant face. music: true (+ music_track) adds a bed from the farm pool (GET /api/music). background (talking-head-split only): "blue"|"green"|"red"|"black"| "purple"|"orange" — the gradient under the presenter; default blue. username/channel/level: identity-bar overrides. resolution: "480p"|"720p" (720p needs a ~90 GB+ GPU; the hosted farm renders 480p and refuses 720p at submit -- the option is for larger self-hosted machines). range: chart span "1y"|"6m"|"2y". - webhook_url (optional): https URL; receives one POST when the render finishes or fails. - variant (optional): publish the finished video to Variant (variantmobile.com). {api_key, channel_id?, as_channel?, title?, badge?, badge_color?}. The Variant key IS the identity (publishes as the avatar it was minted for; no avatar id). as_channel: true publishes as the channel itself with no personal attribution and then channel_id is required. Variant plays as_channel posts faceless (its feed hides the creator block entirely), so as_channel automatically implies is_general: true -- the video renders with no identity bar to match. An explicit is_general overrides in either direction. When the template is trade-video the publish call also carries type: "TRADE", tradePlatform and tradeAssets (derived from the trade facts / platform / ticker), so the post lands on Variant as a TRADE with its venue buttons, not a generic upload. NOTE: publishing to Variant is a restricted capability the Variant account must be granted; the render succeeds without it, only the publish step needs the grant. Returns 202 with the video record: { "id": "...", "template": "...", "ticker": "...", "status": "running", "video_url": null, "created_at": "..." } ### GET /api/v1/videos/{id} — check a render status: queued | running | done | failed. When done: title, video_url, thumbnail_url, finished_at, published_to_variant, variant_video_id (the id Variant returned when the farm published there for you; null otherwise). Ids belonging to other accounts return 404. ### GET /api/v1/videos — list your renders Your 50 most recent, newest first, under "videos". ### GET /api/v1/templates — live template list Only formats the farm can run right now. ## Template catalog - earnings-call — the company's latest earnings call, violet theme. Narration + on-screen points written from management's prepared remarks; verbatim quote from the transcript with its named speaker. No input needed; omit ticker to cover the newest call on a US listing. ~20 min. - recent-event — the most recent actionable SEC filing, forest-green theme. Headline card, presenter over a chart, animated points, verbatim quote from the filing. No input needed; omit ticker for the newest confident catalyst. Refuses tickers whose filings are all routine. ~15-20 min. - catalyst — ~10s alert on a ticker's most recent catalyst. Ticker required. Fastest (~5-8 min). - catalyst-split — the same ~10s alert, split in half: the top half is an 8-second cinematic establishing shot GENERATED for the event (the script pass also writes a purely-visual b-roll direction; a video model renders it in a fixed glossy 3D-animated house style with no text, logos, brand names or people talking) looping over the alert; the bottom half is the presenter on a soft blue gradient, and a black VARIANT / variantmobile.com channel bar rides the seam between the halves. Same inputs as catalyst: ticker required, same events-feed research, same refusal when nothing is gradeable. The shot generates while the avatar renders, so it costs little extra time. - social-clip — presenter over a live 1-year stock chart, or over an X post card when no ticker is given. Ticker or tweet_url required; with both, the card sits above the chart. With a tweet the narration reacts to the post; with only a ticker it is a short take on the company. - chart-head — presenter over the ticker's live chart on a slate field (navy gradient page, matching dark chart card, white identity bar), speaking YOUR material. Required: ticker (the chart is drawn from live market data) plus notes (the model writes the script) or script (spoken verbatim), plus a face (photo_url or the Variant avatar behind variant.api_key). Optional: range "1y"|"6m"|"2y". No feeds are touched: it narrates only what you send. - showdown-no-lipsync — the fight card: a bull-vs-bear debate as generated arcade art, claims spoken per side, a declared winner. Material comes from the Variant insights feed, so variant.api_key with insights access is required. Required: "faces" = {bull, bear} https image URLs (they become the artwork). Optional: "voices" = {side: voice id}, "winner": "bull"|"bear" (default bull). The key publishes the result unless variant.publish is false. Omit ticker to cover the newest insight. The fight card carries no identity bar by construction (only the debated ticker/company appear), so is_general is accepted but changes nothing. Example: https://res.cloudinary.com/dj5d1v6pf/video/upload/v1787957334/variant/examples/fight-card.mp4 - showdown — studio-only: the lip-synced three-hander (narrator, bull, bear). Three avatar generations per render; kept off the API by choice. - talking-head — your presenter over a flat background speaking your own material. Required: notes (model writes the script) or script (verbatim), plus a face. No ticker. - talking-head-split — the split-screen frame (an 8-second cinematic establishing shot GENERATED from your material loops in the top half; the presenter speaks in the bottom half; the black VARIANT / variantmobile.com band rides the seam) carrying talking-head's material model: notes or script required plus a face, no feeds touched, no branded opener, ticker optional (shown as the chip when given). Pick the page with "background": one of "blue" (default), "green", "red", "black", "purple", "orange" — each renders as the same pale-into-deep vertical gradient. With a verbatim script, one small model pass still writes the b-roll direction from it. - trade-video — announce a move on location: the supplied face is re-staged into a scene by an image model before it is animated, so the whole 9:16 frame is the shot, with captions and the closing splash. Pick the scene with "scene": "beach" (lounge chair on a sunny tropical beach, laptop, waves behind them), "limo" (back of a sleek black limousine at night, city lights streaking past the window) or "penthouse" (luxury rooftop terrace over a glittering skyline at dusk); blank picks the beach. Same inputs as talking-head otherwise, with one addition: material is one of script (spoken verbatim, wins), trade (a facts object {platform, asset_in, asset_out, side, chain, market} — the farm's model writes a punchy first-person trade-alert line from it, direction-guarded, ~24 words; asset_in = bought, asset_out = sold, side = buy/sell/long/short; when trade is sent the ticker chip, TRADED ON badge and title derive from the facts automatically), or notes (free text the model narrates). One of the three is required, plus a face (photo_url/photo_b64 or the Variant avatar behind variant.api_key). No ticker required (pass "ticker" anyway, e.g. "ETH", to show the $TICKER chip in the top bar). There is no free-text scene prompt — the scenes are fixed. The clip carries the same identity top bar as every format (handle, channel, tier, ticker chip) plus a TRADED ON {PLATFORM} badge when "platform" is passed (e.g. "Metamask"); publishing with as_channel keeps it general and drops the bar. Intended for short clips ("Just bought some ETH. Come ride the wave with me."). ## Voices American female: af_sarah (default; analyst), af_heart, af_bella, af_nicole, af_sky, af_nova, af_aoede, af_kore. American male: am_liam (default male; natural, recommended), am_puck (energetic, recommended), am_eric, am_michael, am_adam, am_onyx, am_fenrir. British female: bf_emma, bf_alice, bf_isabella, bf_lily. British male: bm_george, bm_daniel, bm_lewis, bm_fable. ## Webhooks If webhook_url was given, a POST on done/failed with the same shape as the status endpoint (including variant_video_id). Delivery is retried on failure or timeout after 30s, 2m and 10m — make the endpoint idempotent, a retry can replay a delivery already processed. It remains one logical POST per terminal outcome, with the same shape as the status endpoint (plus error on failure). ## Limits Free (beta): 12 renders/hour, 40/day per account. Over: 429 with the reason in "error". Paid tiers later; the API shape will not change. ## Errors 400 bad input (body says what) · 401 bad/revoked key · 404 not your video · 429 rate limit · 502 the farm refused (detail says why; the row stays as "failed"). A transient infrastructure failure (an out-of-memory moment, an unreachable upstream, a timeout) is retried once automatically before anything is surfaced: you only ever see terminal outcomes, and finished stage work is reused so the retry is cheap. Deterministic refusals are never retried. ## Example curl -X POST https://variantvideo.com/api/v1/videos \ -H "Authorization: Bearer vv_..." -H "Content-Type: application/json" \ -d '{"template": "earnings-call", "ticker": "NKE", "voice": "am_liam", "photo_url": "https://example.com/face.jpg", "webhook_url": "https://example.com/hooks/variant"}' ## Self-hosting The whole pipeline (models, studio, this same API) is a pip install for your own GPU: pip install video-gen-local && vgl setup && vgl serve. Docs: https://variantvideo.com/docs