API reference

Prefer tools over HTTP calls? The same operations are MCP tools for Claude Code, Cursor and Codex — Connect via MCP.

Base URL: https://app.video-blade.com/api/v1. All endpoints below require your API key unless marked public. Responses are JSON.

Account

GET /me

Your account: id, email, agent mode, chat model, unlimited flag, plan (the subscription plan id, or null — no active plan: generation answers 402 subscription until the user picks one in the dashboard), current balance and agent_connected_at — when an agent first called the API with this key.

GET /balance

Your balance and the current token price.

PATCH /me

{"share_requests": true}

Records the user's answer to the one question protocol start asks when connecting: may Video Blade receive the user's requests to the agent. GET /me returns it as share_requests — null (never asked), true or false. The user can change it any time in Settings; switching it off deletes everything shared so far.

Protocols

Protocols are versioned markdown instructions your agent follows. Both endpoints also accept the key as ?k=, because the agent starts from a single link copied from the dashboard.

GET /protocols/start?k=<API_KEY>

The start protocol as text/markdown, with X-Protocol-Version and X-Protocol-SHA256 headers. Opening it is how the dashboard learns that your agent is connected.

GET /protocols

{"protocols": [{"name": "start", "version": "5", "updated": "2026-10-09", "sha256": "…", "title": "", "category": null,
                "keywords": [], "description": "", "url": "https://app.video-blade.com/api/v1/protocols/start"}, …],
 "categories": [{"name": "music-styles", "title": "Music styles", "protocol": "music", "command": "Make a Song",
                 "prompts": [{"name": "music-rap", "title": "Hip-hop / rap", "keywords": ["rap", "hip-hop", "trap"],
                              "description": "…", "version": "1", "sha256": "…", "url": "…"}]}]}

Before each task the agent compares these hashes with the ones it saved and downloads any protocol that changed. categories are prompt libraries per command: the agent matches the brief against each prompt's keywords and description and reads the fitting prompt by its url — the rules are in protocol conventions, section 9. Protocol mistakes is assembled by the server: its document plus the list of confirmed mistakes — its sha256 changes with every confirmation, so the agent re-reads it when the hash moves.

Requests

POST /requests

{"text": "make a song about our new coffee blend", "summary": "Song about a new coffee blend",
 "command": "music", "project_id": "…", "agent": "Claude Opus 5.5"}

What the user asked the agent, in the user's own words, with the agent's one-line English summary, the protocol it ran as command (other when none fits) and the project. Sent before the first step of the protocol while share_requests is true — the rules are in protocol conventions, section 10: the user's words only, never files, keys or contact details. Answers 201 with the id; 403 sharing is off when the user has not agreed or switched it off — stop sending until GET /me shows it on again. Records are kept for one year and are read by Video Blade to improve the protocols.

Mistakes

POST /mistakes

{"title": "Style line over 200 characters is rejected",
 "what_happened": "POST /songs answered 422 for a 240-character style line.",
 "fix": "Keep the style line under 200 characters; move the rest into the lyrics tags.",
 "command": "music", "project_id": "…", "agent": "Claude Opus 5.5"}

What stopped the agent from following a protocol — the API, a step that was easy to misread, a result that came out wrong — and the fix it found, in the agent's own words: never the user's text, files or keys. Needs no permission from the user. Answers 201 with the id and "status": "new"; 429 while the user already has 30 unreviewed reports. Reports are reviewed by Video Blade; confirmed ones are appended to protocol mistakes, most frequent first, and change its sha256 in the index. The rules are in protocol conventions, section 12.

POST /mistakes/{id}/hit

The agent met a confirmed mistake again: answers {"id", "count"}; 404 for an id that is not in the protocol.

Projects

GET /projects

Your projects, newest first.

POST /projects

{"name": "My channel", "description": "Short videos about cooking"}

Creates a project and records the action in the project's activity log with actor agent.

Project sections

Everything the dashboard shows lives under /projects/{project_id}/…. The agent writes here; the user sees it in the dashboard and can edit by hand. Every write lands in the project activity log with actor agent. Unknown ids answer 404; invalid fields answer 422. A rubric_id that is not a rubric card of the project's matrix answers 422 {"detail": {"error": "unknown rubric", "rubrics": [{id, title}], "hint"}} — pick one of rubrics, leave the field out or create the rubric first (POST …/matrix with section: "rubric"); a trend whose url is already filed in the project answers 409 {"detail": {"error": "duplicate", "item_id"}}.

GET /projects/{id} · PATCH /projects/{id}

PATCH {"name", "description", "status": "active|archived", "video_format": "9:16|16:9|1:1",
       "models": {"image": "<slug>", "video": null, "music": "<slug>"},
       "languages": {"content": ["en-US"], "work": "ru"},
       "story_style": "<prompt name of category story-styles>"}

story_style is the project's default story style — the agent offers it first when a new video starts (the prompts of category story-styles); a name that is not such a prompt answers 404.

Project card and settings. GET also returns counts per section. Model slugs come from GET /models. languages (BCP 47 codes, null until set): content — what the audience hears and reads (scripts, voice-over, captions, songs; several allowed, the first is the main one), work — the language the agent writes everything for the owner in (the brief, the strategy, the matrix, plans, reports). A field you send replaces its own value; the other one stays.

GET /projects/{id}/activity

The activity log, newest first: who (human / agent), what and when.

GET /projects/{id}/strategy · PUT /projects/{id}/strategy

PUT {"product", "audience", "promise", "proof", "cta", "demand_type": "hot|created|mixed", "notes"}

GET returns the same fields plus matrix — all matrix cards.

GET /projects/{id}/brief · PUT /projects/{id}/brief

PUT {"questions": [{"id", "topic", "q", "a", "source", "confirmed", "agent_a"}]}

The strategy brief: the questions the agent asks before it builds a strategy — at least 30, grouped by topic (the user sees them on the Brief tab of the strategy page and answers there or in the agent's local copy). PUT replaces the whole brief: send every question and keep the id of existing ones; a question without id gets one. source marks an answer the agent filled in from data it already had (where it came from); confirmed — the user confirmed or corrected it; agent_a — the agent's answer the user corrected, kept so it can be restored. Send confirmed and agent_a back as you got them. Both calls answer {"questions", "updated_at", "count", "answered", "suggested", "target", "done"}: suggested — filled-in answers still waiting for the user's confirmation, done — at least target questions, all answered and confirmed.

Collections: one CRUD shape

Each collection below works the same way: GET /projects/{id}/<path> lists ({"items": […]}), POST creates (201), GET /<path>/{item_id} reads one, PATCH updates the fields you send, DELETE removes ({"ok": true}).

PathFieldsList filters
/matrixsection (audience_segment, artifact, hunt_ladder, rubric, theme_mood, angle, goal, length_strategy, hook, facture, format, props, reference), title, description, data (the section's fields — audience_segment: jtbd, pains, desires, objections, situations, language, platforms (lists of strings); hunt_ladder: stage (1–9), segment_id, message; rubric: goal_type (reach, warmup, funnel_entry), target_share (%); goal: goal_type, target_share, metric; theme_mood: mood; angle: angle_type; length_strategy: length (long, short, reels), use_for; hook: hook_type, worked; facture: status (enough, need_more, missing), facture_type; reference: url, inspiration_id), sort_order, rubric_id (optional id of the rubric card the card belongs to — the cabinet shows the matrix as a grid of rubrics × dimensions). Deleting a card clears every link to it (plan coordinates, rubric of plan items, references and ready videos, the segment of ladder rungs).?section=
/plantitle, idea, status (idea, script, production, editing, ready, published), format (long, short, reels), platforms, publish_date (YYYY-MM-DD), rubric_id, scenario_id (the video's script in /scenarios), notes, matrix {segment_id, ladder_stage (1–9), angle_id, hook_id, goal_id} (the video's coordinates in the matrix; the whole object is written), color (red, orange, yellow, green, teal, blue, purple, pink, grey or null — the card color), filmed (the footage is shot), result_links [{platform, url}] (where it was published), metrics {views, likes, comments, leads, conclusion} (results and what worked — the conclusion goes back into the matrix), sort_order (order inside a board column)?status=
/scenariostitle, logline, status (draft, writing, ready, in_production, used), format, target_duration_sec, body (markdown: the context of the script — the AV table is rendered from scenes), notes, story_style (the name of a prompt of category story-styles, chosen at the start of the video), lines [{title, color, character_id, want, obstacle}] (storylines: whose story, what they want, what stands in the way; up to 5), scenes [{line_ids, start_sec, duration_sec, place, characters, action, change_from, change_to, opens, closes, bridge, viewer, kind: live|ai, shots: [{size: wide|medium|close|insert, move, action, voice, text, sound, duration_sec, frame_asset_id, clip_media_id, status: none|frame|clip|approved}]}] (the story board: a scene is a point on one or two lines; its shots are the storyboard frames and, later, the clips; lists are written whole, ids are set by the server)?status=
/inspirationkind (reference, idea, trend), title and url (at least one of them), media (video, audio, link, file — what the reference is), priority (low, medium, high — for ideas), text (the video's transcript, up to 200,000 characters), description (the post's description), audience_comments [{author, text, likes}] (viewer comments from the video page), notes, rubric_id, reviewed (re-watched; reviewed_at is set with it), marks (read-only: what was done with the reference — see marks below), fetch (read-only: the last link reading — status running, done or failed, error, missing parts)?kind=
/charactersname, description (the look — the exact phrase you reuse in prompts), voice (how the voice sounds), voice_id (from the user's voice tool), voice_settings {stability, similarity, style (0–1), speed (0.5–2), speaker_boost}, photos (read-only here: [{id, url, view, note, prompt, generation_id}] — see the photo endpoints below; view is the angle: front, three_quarter, left, right, back, top, bottom, face, close, far, other; the first photo is the main one), audio (read-only: [{id, url, name, duration}] — voice samples), photo_error (read-only: why the last drawn photo failed, null after a success)—
/trackstitle, style, prompt, lyrics (their earlier values are kept in read-only history: up to three per field, newest first, with who saved them), status (draft, generating, ready, published), favorite, versions [{url, image_url, duration, liked, reaction (like | dislike | null), note, generation_id, uploaded, style (read-only: the style the version was ordered with)}], tags (up to 10 short labels; the dashboard filters the song list by them), settings {model: v5|v5.5|v6, vocal: m|f, instrumental, negative_tags, language, weirdness, style_weight, audio_weight (sliders 0–100), duration (seconds 10–360 or “3:30”) and verses (1–8) — the lyrics brief: how long and how many verses the lyrics are written for; Generate does not send them to the model}, source (read-only: the source audio of a cover), last_error (read-only: why the last run failed, null after a successful one)?status=
/assetskind (image, audio, video), category (scene, cover, background, other), title, prompt, url, thumb_url, model, favorite, notes, generation_id, series_id (the image series the picture belongs to, see /image-series), series_at (read-only: when the picture was moved into its series from the cabinet; the picture's number in the series follows the order the pictures joined it), source (generated, uploaded, stock, link), source_url, duration, width, height, session_id (the video the clip was made for), stock (read-only: provider, id, author, author_url, license — set when the file came from a stock bank)?kind=&category=&series_id=
/image-seriesone series = one request: title, prompt, category, model (a slug from GET /models?category=image that draws from text; empty — the default model), aspect_ratio (1:1, 3:4, 4:3, 9:16, 16:9; empty — the project's video format), resolution (1K, 2K), count (variants per order, 1–4), notes, references [{url, title}] (up to 8 pictures the model keeps to — a character, a real product, a style anchor; what must carry over goes first), last_error (read-only: why the last order failed, null after a success). Drawn pictures land in /assets with this series_id.?category=
/video-sessionstype (shorts, clip, cartoon, avatar), title, brief, status (draft, in_progress, review, done), scenario_id, stages [{name, status: pending|running|done|failed, note}], media [{kind, url, title, thumb_url, duration, asset_id, source}] (generated clips, stock footage and uploads land here by themselves — return the server's fields as you got them), cuts [{title, url, duration, thumb_url}], comments (read-only here; see the comments endpoint), last_error (read-only: why the last clip failed, null after a success)?status=
/albumstitle, description, artist (shown on the site and in the zip's tags; empty: the project's name), cover_url (a project image or your own URL), published (the album's page on the site), tracks [{track_id, version_id, title}] — specific versions of the project's songs in order, each with its own title (empty: the song's title)?published=
/readytitle, video_url, thumb_url (taken from the video by the server when empty), status (ready, posted), budget_tokens, rubric_id, session_id, notes (the caption for the post), links [{platform: tiktok|youtube|instagram|facebook|other, url, posted_at, views, likes, comments}]?status=

Rows in nested lists (versions, stages, media, cuts, links, result_links) get an id from the server; PATCH with the whole list replaces it — send the current list with your changes applied.

GET /projects/{id}/inspiration-actions · POST · PATCH …/{action_id} · DELETE …/{action_id}

POST {"title": "Take hook", "emoji": "🪝", "description": "Reuse only the first seconds", "active": true, "sort_order": 3}

The project's actions for references — what can be done with a video (Reshoot, Copy, New rubric, Take hook, Study by default). They are the options of the dashboard's “Mark” menu and the buttons of the project's Telegram bot; active: false hides one. Deleting an action keeps the marks already made with it.

POST /projects/{id}/inspiration/{item_id}/marks · DELETE …/marks/{mark_id}

{"action_id": "…", "note": "took the cold open only"}

Marks the reference as processed with an action — every mark is a dated row of its history, repeats are allowed. The reference carries them in marks [{id, action_id, title, emoji, source: web | bot | api, note, at}], oldest first. Answers 201 with the reference; an unknown action or reference — 404.

POST /projects/{id}/inspiration/{item_id}/fetch

Reads the reference's link in the background: the post's description, the video's transcript and viewer comments (YouTube, TikTok, Instagram, Facebook, X, Threads, Reddit). Costs 1 token, charged only when something was read. A reference without a title gets the first line of the post's description as its title. Answers 202 {"fetch": {"status": "running", …}, "price_tokens": 1}; poll GET …/inspiration/{item_id} until fetch.status is done (the parts that couldn't be read are in fetch.missing) or failed. Refusals: unsupported link — 422, not enough tokens — 402, already reading — 409, reading unavailable — 503; the body is {"detail": {"error": "<code>"}}.

POST /projects/{id}/scenarios/{item_id}/produce

{"type": "shorts"}

“Send to production”: creates a video session from the script, marks the script in_production, answers 201 with the session.

POST /projects/{id}/{tracks | albums | image-series | characters | video-sessions}/{item_id}/comments · DELETE …/comments/{cid}

{"text": "Second chorus feels weak — rewrite?"}

Comments on a song, an album, an image series or a character: what the user did and what they want, and what the agent did — the record's Comments tab in the dashboard. The record carries them in comments [{id, author: human | agent, text, at}], oldest first. Answers the record; an unknown record — 404.

POST /projects/{id}/tracks/{item_id}/source?filename=source.mp3

curl -X POST ".../tracks/{item_id}/source?filename=source.mp3" -H "X-API-Key: vb_…" -H "Content-Type: audio/mpeg" --data-binary @source.mp3

Attaches the source audio of a cover: the request body is the file itself — MP3, M4A, WAV, OGG or FLAC, from 6 seconds to 8 minutes, up to 30 MB, or a video with the recording (up to 150 MB; the audio is taken out of it). Answers with the track; its source holds the stored file. A track with a source is generated as a cover: the melody and the length come from the recording, the lyrics and the style from the track. The uploaded recording also appears among the track's versions with uploaded: true.

POST /projects/{project_id}/tracks/{track_id}/source/pick

Body {"version_id": "…"}: one of the track's own versions becomes the source of a cover (a remix of what the model already sang) — no file to upload. Answers with the track.

POST /projects/{id}/tracks/{item_id}/versions/{rid}/publish · POST …/unpublish

Body {"title": "…"}: the version goes to the site's music library (GET /public/songs) under the public title (empty: the track's title); a repeat updates the title. unpublish takes it down. Answers {"published": true, "song_id": "…", "title": "…"}.

POST /projects/{id}/albums/{album_id}/cover

The album's cover as a file: the request body is the image (JPEG, PNG or WebP up to 10 MB; the type is read from the bytes); answers with the album. A project image can be set with PATCH cover_url instead.

POST /projects/{id}/albums/{album_id}/cover/generate

{"prompt": "A lone figure on a rooftop facing a thunderstorm over a night city, 35mm, cinematic, muted blue and amber"}

Draws the cover: a square picture by the description (the mood of the songs). The service adds the album title and the artist name (artist, else the project name) to the prompt and asks the model to letter them on the cover — do not describe lettering in prompt. Answers 202 {"generation_id", "status": "running", "tokens_held"} — the tokens are held at once and charged when the picture arrives; a failed run returns them. In 1–2 minutes the album's cover_url holds the picture and cover_error is null; on failure cover_error holds the reason code. One cover per album at a time (409); not enough tokens — 402.

GET /projects/{id}/albums/{album_id}/tracks · POST /projects/{id}/albums/{album_id}/tracks

The album's tracks with the versions' files: n, title, song_title, version_number, url, duration, missing (the version was deleted). POST with {"track_id": "…", "version_id": "…"} adds a version to the end; order and titles — PATCH /albums/{album_id} with the whole tracks list.

POST /projects/{id}/characters/{item_id}/photos?view=front&note=… · POST …/photos/link

curl -X POST ".../characters/{item_id}/photos?view=front&note=talking%20head" -H "X-API-Key: vb_…" -H "Content-Type: image/png" --data-binary @front.png
curl -X POST ".../characters/{item_id}/photos/link" -H "X-API-Key: vb_…" -H "Content-Type: application/json" -d '{"url": "https://…/photo.jpg", "view": "left", "note": "profile for walking scenes"}'

A character's photo, kept by angle: the request body is the image (JPEG, PNG or WebP up to 10 MB; the type is read from the bytes), or /photos/link with a URL — the server downloads the picture into the user's storage, so the link may expire later. view is the angle (default other), note says what the photo is for. Answers 201 with the character; a picture that is not an image or cannot be downloaded answers 422 (photo_not_image, photo_link), one over 10 MB — 413, more than 40 photos — 409 photos_limit.

POST /projects/{id}/characters/{item_id}/photos/generate

{"prompt": "Standing in a bright kitchen, arms crossed, warm side light, 35mm", "view": "three_quarter", "aspect_ratio": "3:4", "note": "presenter shots"}

Draws a photo of the character (prompt may be empty — then a neutral reference shot of the angle: plain background, soft light): the character's existing photos go to the model as references, each labelled with its angle (the requested angle first, then the front view, then the rest — up to eight), together with the description; the prompt adds the pose, outfit, setting and light. aspect_ratio: 1:1, 3:4, 4:3, 9:16 or 16:9 (empty: the project's video format). Answers 202 {"generation_id", "status": "running", "tokens_held"}; in 1–2 minutes the photo appears in the character's photos with its view, note, prompt and generation_id, and photo_error is null; on failure photo_error holds the reason code and the tokens come back. Up to three photos of one character at a time (409 photos_in_progress); not enough tokens — 402.

PATCH /projects/{id}/characters/{item_id}/photos/{rid} · POST …/photos/{rid}/main · DELETE …/photos/{rid}

PATCH with {"view", "note"} changes the angle and the note; main moves the photo first — it becomes the character's avatar; DELETE removes the row (the file stays in storage). Unknown row — 404.

POST /projects/{id}/characters/{item_id}/audio?filename=sample.mp3 · DELETE …/audio/{rid}

A voice sample: the request body is the recording itself (MP3, M4A, WAV, OGG or FLAC up to 30 MB); answers 201 with the character, the sample is in audio with its duration. Not a recording — 422 source_not_audio.

POST /projects/{id}/image-series/{item_id}/generate

{"count": 2}

Draws the series' pictures with its prompt, model, aspect_ratio, resolution and references — one order per variant (count from the body or the series). When references are attached, a text-to-image model switches to its image-to-image twin (Flux-2, GPT Image, Seedream, Qwen3, Grok); Nano Banana and Wan take references as they are. Answers 202 {"generation_ids": [...], "status": "running", "tokens_held"} — the tokens are held per variant and charged as each picture arrives; a failed variant returns its tokens. In 1–2 minutes the pictures appear in GET /assets?series_id=… (newest first) with the prompt, the model and generation_id. Up to eight pictures of one series at a time (409 series_in_progress); empty prompt — 422 no_series_prompt; the plan's limit of orders running at once — 422 concurrency; not enough tokens — 402.

POST /projects/{id}/image-series/{item_id}/references · DELETE …/references/{rid}

{"character_id": "…"}   or   {"asset_id": "…"}   or   {"url": "https://…", "title": "anchor"}

A reference picture of the series: a character's main photo, a project image or any URL. Answers 201 with the series; a picture that is already in the list is not added twice (the series comes back as is); more than eight — 409 references_limit. A project image that belongs to a series is titled <series> · #N — its number in that series, the same number the cabinet shows.

POST /projects/{id}/tracks/{item_id}/generate

{"generation_id": "…", "status": "running", "mode": "cover", "tokens_held": 16}

Orders a song (or a cover, when the track has a source) with the track's saved settings, style and lyrics; answers 202. Up to three runs of one track go at once. The price is the one GET /models?category=music shows; the tokens are held at once and charged when the versions arrive — a run that fails returns them in full. In 2–4 minutes the track gets two new versions and the status ready. A refusal is {"detail": {"error": "<code>", "message": "…"}}: 402 insufficient, 409 in_progress, 422 concurrency (the plan's limit of orders running at once), 413 source_too_big, 503 unavailable or busy, 422 for everything the track or the file must fix first (no_idea, no_style, lyrics_too_long, source_not_audio, source_length, rejected, content).

POST /projects/{id}/assets/upload?filename=clip.mp4&title=…&session_id=…&category=…

Your own picture or clip into the project library — the file is the request body (curl --data-binary @clip.mp4); the type is read from the bytes (jpg, png, webp, mp4, mov, webm), pictures up to 10 MB, video up to 300 MB. With session_id the file also lands in that session's media. For a video the server stores a preview (thumb_url) and the duration. Answers 201 with the asset. Not a picture or video — 422 upload_not_media; too large — 413 upload_too_big.

POST /projects/{id}/assets/import

{"url", "title"?, "session_id"?} — a file from a trusted site is copied into the user's storage (the administrator keeps the list of trusted sites; the stock banks' sites are always allowed). Other addresses — 422 link_untrusted; a file that did not download — 502 link_failed.

GET /projects/{id}/stock/sources · GET /projects/{id}/stock/search?q=&kind=video|image&orientation=portrait|landscape|square&page=1 · POST /projects/{id}/stock/save

Free footage and photos from stock banks, licensed for commercial use. sources lists the connected banks and the trusted sites; search returns {"items": [{provider, id, kind, title, author, author_url, source_url, preview_url, preview_video_url, width, height, duration}], "sources"} — orientation defaults to the project's video format. save with {"provider", "id", "kind", "session_id"?} copies the file into the user's storage once (a second save of the same item reuses it) and answers 201 with the asset (source: "stock", the author, the source page and the licence in stock); with session_id it goes into that session's media. No banks connected — 503 stock_unconfigured; the bank did not answer — 502 stock_failed; too many requests — 429 stock_busy.

GET /projects/{id}/video-models

The models that can make a clip for this project: {"models": [{slug, name, unit: "per second" | "per video", prices: {resolution: tokens}, durations, resolutions, aspects, start, start_required, end, audio}], "default"}. Prices are in tokens per unit; the project's video format is the clip's aspect. Models with no parameter description in the catalog are not listed.

POST /projects/{id}/video-sessions/{item_id}/generate · GET …/status

{"prompt", "model"?, "start_url"?, "end_url"?, "duration"?, "resolution"?, "audio"?, "title"?} orders one clip for the session: the prompt in English, start_url a still (an asset of the project, a character photo or a public image) the clip starts from, end_url the target frame for a morph (models with end), duration one of the model's lengths, audio only for models that make sound. Answers 202 {"generation_id", "status": "running", "tokens", "model", "model_name"} — the tokens are held at once, charged when the clip arrives, returned on failure. status → {"active", "last_error"}: poll it every 15–30 seconds; the finished clip appears in the session's media (source: "generated") and in GET /assets?kind=video. Up to three clips of one session at a time (409 session_in_progress); empty prompt — 422 no_clip_prompt; a model from outside /video-models — 422 model_unsupported; a model that needs a start frame without one — 422 start_frame_required; the plan's limit of orders running at once — 422 concurrency; not enough tokens — 402.

POST /projects/{id}/video-sessions/{item_id}/media/from-library · POST …/cuts/upload?filename=clean_v1.mp4&title=…

from-library with {"asset_id"} puts a library item into the session's media (the same item is not added twice). cuts/upload takes an assembled version as the request body (video up to 300 MB), stores it, takes the preview and the duration, and answers with the session.

POST /projects/{id}/ready/upload?filename=final.mp4&title=…&session_id=…&rubric_id=…&budget_tokens=…&notes=…

A finished video as a file (request body, up to 300 MB) — a ready video with its preview; the other fields as in POST /ready. Answers 201 with the item.

Models

GET /models?category=image public

The model catalog with the price in tokens per unit (exactly what a task charges) and parameter names. Use it to let the user pick one model per project per category.

Public endpoints no key

These answer without an API key and carry Access-Control-Allow-Origin: *, so a website can call them from the browser. They only return what is already public. Base URL for websites: https://api.video-blade.com/api/v1.

GET /public/site

{"pricing": {…same as /public/pricing…}, "faq": [{"q", "a"}, …],
 "powered_by": ["🎬 Veo 3.1", …], "texts": {"hero.description": "…", …}}

Everything video-blade.com shows, in one request: prices, the FAQ, the “Powered by” strip and block texts. Texts and the FAQ are edited in the admin panel; placeholders such as {creator.price} arrive replaced with current numbers, and **bold** marks bold text.

GET /public/pricing

{"currency": "USD", "token_price_usd": …,
 "packages": [{"id", "name", "tokens", "price_usd", "recommended"}, …],
 "storage": {"included_gb", "price_usd_gb_month", "traffic_included"},
 "tax_included": true,
 "plans": [{"id", "name", "price_usd_month", "price_usd_year", "tokens_month", "storage_gb", "concurrency", "recommended",
            "purchasable", "seats", "seats_left", "sold_out"}, …],
 "yearly_discount_percent": …,
 "rates": [{"id", "label", "model", "detail", "tokens", "price_usd"}, …],
 "models": {"image": …, "video": …, "music": …, "audio": …, "chat": …},
 "enterprise": {"from_usd_month": …, "email": "…"}, "founders": {"seats": 100, "taken": …}}

The pricing section of video-blade.com renders from this response instead of hard-coding numbers. Keys are a contract: they are never renamed, and new keys are appended. When tax_included is false, applicable taxes are added at checkout.

GET /public/models

{"currency": "USD", "token_price_usd": …,
 "models": [{"slug", "category", "unit", "tokens", "price_usd", "orderable",
             "variants": [{"label", "tokens", "unit", "price_usd"}, …], "from_tokens", "from_usd"}, …]}

Live prices of every catalog model for the model pages of video-blade.com (/models/…): the price per unit and per option in tokens and USD, exactly what a task charges. orderable is false for models listed only for their price — they cannot be ordered through the API yet. from_tokens / from_usd — the cheapest option a task is charged for (input fees are not part of it). Keys are a contract: never renamed, new keys appended.

GET /public/founders

{"seats": 100, "taken": …, "items": [{"seat": 1, "name": "…" | null}, …]}

The Founders wall of video-blade.com: the seats of current Founder-plan subscribers in seat order. name is set only when the founder chose to show it in the dashboard settings; otherwise the seat is listed by number alone. seats is the total number of seats, taken — how many have been issued (seats are never reissued).

GET /public/songs?q=&style=&limit=50&offset=0

Published songs from the AI Music library, newest first: {"items": [{"id", "title", "audio_url", "image_url", "duration", "style", "tags", "created_at"}], "total", "limit", "offset"}. q searches title, style and tags; style filters by style or tag.

GET /public/featured-songs?limit=6

Songs marked as featured for the home page, same item shape.

GET /public/albums?limit=50

Published albums, newest first: {"items": [{"id", "title", "cover_url", "artist", "tracks" (count), "created_at"}]} — the album cards on the site's music page.

GET /public/albums/{album_id}

{"id": "…", "title": "Debut", "description": "…", "cover_url": "…", "artist": "Project name",
 "tracks": [{"n": 1, "title": "…", "audio_url": "…", "duration": 210.0}], "created_at": "…"}

A published album for its page on the site (video-blade.com/album?id=…); unpublished — 404.

GET /public/faq

The FAQ of video-blade.com in display order: {"items": [{"q", "a"}]}. Placeholders such as {creator.price} in the texts arrive already replaced with current numbers from the system.

Generations

There is no standalone generation endpoint: every order belongs to a record of the project and is documented with it above — POST /projects/{id}/image-series/{item_id}/generate (the pictures of a series), …/video-sessions/{item_id}/generate (a clip), …/tracks/{item_id}/generate (a song or a cover), …/albums/{album_id}/cover/generate (a cover), …/characters/{item_id}/photos/generate (a character photo). Each answers 202 with the generation id and the tokens held; the result appears in the record itself — poll it. Media URLs in results point to Video Blade storage.

Errors

StatusMeaning
401Missing or invalid API key
402Insufficient tokens for the requested task
404Unknown model, project or record
422Invalid input for the model (see the model page)