Build with Vuesy
Turn a script, or your own voiceover, into a finished documentary-style video from your code, from n8n or Make, or from an AI agent like Claude or Cursor. Same videos, same credits as the studio.
- Plans
- Included in every paid plan (Creator, Studio and Agency)
- REST API
https://vuesy-app-test.684108.xyz/v1- MCP server
https://vuesy-app-test.684108.xyz/mcp
Quickstart
Your first video in three steps. Each program creates a short video, waits for it, exports the MP4 and prints the download link.
- Create an API key
In the app, open API & MCP and click Create API key. It is shown once: store it somewhere safe.
Open API & MCP - Put it in your environment
export VUESY_API_KEY="vuesy_..." - Run one of these
#!/usr/bin/env bash # Vuesy API quickstart (curl + jq): script -> video -> MP4 download link set -euo pipefail API=https://vuesy-app-test.684108.xyz/v1 AUTH="Authorization: Bearer $VUESY_API_KEY" # 1. Create the video (the Idempotency-Key makes a retry safe: it never charges twice) VIDEO=$(curl --fail-with-body -sS -X POST "$API/videos" -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-$(date +%s)" \ -d '{"script": "In 1969, three astronauts left Earth on a journey nobody had made before. Four days later, two of them walked on the Moon while the whole planet watched.", "title": "Apollo 11 in 30 seconds"}') ID=$(echo "$VIDEO" | jq -r .id) echo "video $ID: $(echo "$VIDEO" | jq -r .status), $(echo "$VIDEO" | jq -r .cost_credits) credits" # 2. Wait until it is ready while true; do STATUS=$(curl --fail-with-body -sS "$API/videos/$ID" -H "$AUTH" | jq -r .status) echo "status: $STATUS" case "$STATUS" in succeeded) break;; failed|cancelled|interrupted) exit 1;; esac sleep 10 done # 3. Export it to MP4 (the first export of a video is free) and wait for the link EXPORT=$(curl --fail-with-body -sS -X POST "$API/videos/$ID/export" -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-export-$ID" -d '{"quality": "medium"}') EID=$(echo "$EXPORT" | jq -r .id) while true; do E=$(curl --fail-with-body -sS "$API/exports/$EID" -H "$AUTH") case "$(echo "$E" | jq -r .status)" in succeeded) break;; failed|cancelled|interrupted) exit 1;; esac sleep 10 done echo "download: $(echo "$E" | jq -r .download_url)"# Vuesy API quickstart (Python 3 + requests): script -> video -> MP4 download link import os, time, uuid import requests API = "https://vuesy-app-test.684108.xyz/v1" s = requests.Session() s.headers["Authorization"] = "Bearer " + os.environ["VUESY_API_KEY"] def call(method, path, **kw): """One request, retrying politely on 429/503 (the API tells you how long to wait).""" for _ in range(6): r = s.request(method, API + path, timeout=60, **kw) if r.status_code in (429, 503) and "Retry-After" in r.headers: time.sleep(min(int(r.headers["Retry-After"]), 120)) continue if r.status_code >= 400: raise SystemExit("API error %s: %s" % (r.status_code, r.json().get("error"))) return r.json() raise SystemExit("API still busy (%s): try again later" % r.status_code) def wait(path): while True: obj = call("GET", path) print(obj["object"], obj["id"], obj["status"]) if obj["status"] == "succeeded": return obj if obj["status"] in ("failed", "cancelled", "interrupted"): raise SystemExit("%s %s: %s" % (obj["object"], obj["status"], obj["error"])) time.sleep(10) video = call("POST", "/videos", headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "script": "In 1969, three astronauts left Earth on a journey nobody had made before. " "Four days later, two of them walked on the Moon while the whole planet watched.", "title": "Apollo 11 in 30 seconds", }) print("video", video["id"], "costs", video["cost_credits"], "credits") wait("/videos/" + video["id"]) export = call("POST", "/videos/%s/export" % video["id"], headers={"Idempotency-Key": "export-" + video["id"]}, json={"quality": "medium"}) done = wait("/exports/" + export["id"]) print("download:", done["download_url"])// Vuesy API quickstart (Node.js 18+, no dependencies): script -> video -> MP4 download link import { randomUUID } from "node:crypto"; const API = "https://vuesy-app-test.684108.xyz/v1"; const headers = { Authorization: `Bearer ${process.env.VUESY_API_KEY}`, "Content-Type": "application/json" }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function call(method, path, body, idempotencyKey) { for (let i = 0; i < 6; i++) { const h = idempotencyKey ? { ...headers, "Idempotency-Key": idempotencyKey } : headers; const r = await fetch(API + path, { method, headers: h, body: body ? JSON.stringify(body) : undefined }); if ((r.status === 429 || r.status === 503) && r.headers.get("retry-after")) { await sleep(Math.min(Number(r.headers.get("retry-after")), 120) * 1000); continue; } const data = await r.json(); if (!r.ok) throw new Error(`${r.status}: ${data.error}`); return data; } throw new Error("still busy, try again later"); } async function wait(path) { for (;;) { const obj = await call("GET", path); console.log(obj.object, obj.id, obj.status); if (obj.status === "succeeded") return obj; if (["failed", "cancelled", "interrupted"].includes(obj.status)) throw new Error(obj.error); await sleep(10000); } } const video = await call("POST", "/videos", { script: "In 1969, three astronauts left Earth on a journey nobody had made before. " + "Four days later, two of them walked on the Moon while the whole planet watched.", title: "Apollo 11 in 30 seconds", }, randomUUID()); console.log("video", video.id, "costs", video.cost_credits, "credits"); await wait(`/videos/${video.id}`); const exp = await call("POST", `/videos/${video.id}/export`, { quality: "medium" }, `export-${video.id}`); const done = await wait(`/exports/${exp.id}`); console.log("download:", done.download_url);
AI agents (MCP)
Vuesy has an MCP server, so AI agents such as Claude Code, Claude Desktop and Cursor can make videos for you. Tell your agent "make me a 10-minute history video about the fall of Rome" and it writes or fetches the script, creates the video, waits for it and hands you the download link.
- Remote server:
https://vuesy-app-test.684108.xyz/mcp(Streamable HTTP). Nothing to install. - Same account, same prices. The agent uses your API key (or the access you approve when it signs in), so it gets the same credits, plan limits and permissions as the web app. Nothing extra is charged for using MCP.
1. Get an API key
Open vuesy.dev/app → API & MCP in the sidebar → Create API key. Copy it (it starts with vuesy_); it is shown only once. API keys need a paid plan. You can give a key a daily credit cap to limit what an agent can spend per day.
2. Connect your agent
Replace vuesy_YOUR_KEY with your key.
Or sign in instead of using a key: apps that support MCP sign-in (OAuth), such as Claude Code, can connect with only the URL https://vuesy-app-test.684108.xyz/mcp. You sign in to Vuesy in the browser and approve the app; it then shows under Connected apps on the API & MCP page, where you can disconnect it at any time. A connected app has every permission of a key and no daily credit cap.
Claude Code
One command:
claude mcp add --transport http vuesy https://vuesy-app-test.684108.xyz/mcp \
--header "Authorization: Bearer vuesy_YOUR_KEY"
Without --header, Claude Code signs you in through the browser instead (run /mcp in Claude Code and pick vuesy). claude mcp list should say Connected (that only checks the connection, not the key): to check the key, ask Claude "How many Vuesy credits do I have?". Add --scope user to use it in every project.
Claude Desktop
Settings → Developer → Edit Config, then put this in claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json) and restart Claude Desktop. It needs Node.js 20 or newer.
{
"mcpServers": {
"vuesy": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://vuesy-app-test.684108.xyz/mcp",
"--header",
"Authorization:${VUESY_AUTH}"
],
"env": {
"VUESY_AUTH": "Bearer vuesy_YOUR_KEY"
}
}
}
}
Claude Desktop runs local servers, so this uses mcp-remote (it needs Node.js 20 or newer) to connect to the remote server with your key. Restart Claude Desktop after saving.
Cursor
Put this in ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"vuesy": {
"url": "https://vuesy-app-test.684108.xyz/mcp",
"headers": { "Authorization": "Bearer vuesy_YOUR_KEY" }
}
}
}
Other MCP clients
Any client that lets you set an HTTP header for a remote MCP server: URL https://vuesy-app-test.684108.xyz/mcp, header Authorization: Bearer vuesy_YOUR_KEY. For apps that only run local servers, use mcp-remote as in Claude Desktop above.
Quick check from a terminal (asks for your credit balance, so it also checks the key):
curl -s https://vuesy-app-test.684108.xyz/mcp -H "Authorization: Bearer vuesy_YOUR_KEY" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_credits","arguments":{}}}'
3. Ask for a video
Example prompts:
- "Make me a 2-minute video in English about the history of the Roman aqueducts. Write the narration yourself, tell me the price first, then give me the download link."
- "Write a 10-minute script about the fall of Constantinople in the style of https://www.youtube.com/@MyFavouriteChannel, show it to me, and when I say OK turn it into a video with an epic soundtrack and the cinematic background."
- "List Spanish voices, pick a deep male narrator, and make a 5-minute video in Spanish from this script: …"
- "How many credits do I have left, and what did my last videos cost?"
- "Make a YouTube thumbnail for my video 'The Last Days of Pompeii' like the ones on https://www.youtube.com/@MyChannel."
Videos take several minutes to render. The agent gets a video id at once and then checks on it (about once a minute, or by waiting up to 50 seconds per check). If your agent cannot wait, it gives you the video id: ask again later, or find the video in the web app.
Tools
| Tool | What it does | Spends credits |
|---|---|---|
get_credits |
Balance, credits held by running jobs, this key's daily cap | no |
get_credit_history |
Recent credit movements | no |
get_account |
Plan, this key's limits and today's usage | no |
estimate_cost |
Price of a video by length, language and voice | no |
list_languages |
Narration languages | no |
list_voices |
Narrator voices of a language | no |
list_music_tracks |
Background music catalog by mood | no |
find_reference_videos |
Videos of a YouTube channel to imitate | no |
generate_script / get_script |
AI script writer (imitates 1-3 reference videos) | plan allowance, then a few credits |
create_video / get_video |
Create a video from a script, a generated script or your own voice-over; follow its progress | yes |
list_videos |
Your videos (web and API) | no |
cancel_video |
Cancel a queued or running video or export (refunded) | no |
export_video / get_export |
1080p MP4 and its download link (the first export of a video is normally free) | re-exports |
list_exports |
Your exports with fresh download links | no |
create_upload |
Upload link for your own voice-over or music | no |
generate_thumbnail / get_thumbnail |
AI YouTube thumbnail | plan allowance, then a few credits |
The agent also gets a ready-made prompt, make_video, with the whole flow.
Billing, limits and safety
- Everything is billed exactly like the web app, to the account that owns the key. Creating a video reserves its credits; a failed or cancelled video is refunded automatically.
- Repeating the same request on the same day (UTC) returns the same video instead of charging again, so an agent that retries after a timeout never pays twice. If that earlier video failed or was cancelled, a new one is made. To make a second identical video on purpose (or re-export after editing the project in the web editor), the agent passes
force_new. max_cost_creditsoncreate_videois a hard ceiling for one video. A key's daily credit cap is checked before each new video.- Limits are your plan's API limits (requests per minute, videos in progress). When one is hit, the agent is told how long to wait.
- The MCP server is part of the Vuesy API: it calls the same
/v1endpoints with your key and keeps nothing of its own. - Revoke a key, or disconnect an app, at any time in the web app; the agent loses access at once.
- Not available through MCP, on purpose: billing and plans, API keys and account settings. These stay in the web app.
Keys & authentication
Send the key on every request as Authorization: Bearer vuesy_... (or X-API-Key: vuesy_...).
All calls are HTTPS to https://vuesy-app-test.684108.xyz/v1 with JSON bodies.
- Create, name and revoke keys in the app (API & MCP in the sidebar). The full key is shown once; we only keep a hash and show its first characters.
- Give each integration its own key, with only the permissions it needs:
videos:write,videos:read,scripts:write,thumbnails:write,credits:read. Optionally set a daily credit limit per key (a good idea for AI agents). - Never put a key in a web page or a mobile app: call the API from your server or automation tool.
- If a key leaks, revoke it: it stops working at once.
How a video is made
POST /v1/videoswith your script (oraudio_key). The price is reserved from your credits; you get an id.- Poll
GET /v1/videos/{id}every 20–30 s.status:queued→running(withprogress.percent) →succeeded, orfailed/cancelled(credits come back:refunded: true). A 1-minute video usually takes a few minutes. POST /v1/videos/{id}/exportrenders the 1080p MP4 (the first export of each video is free).- Poll
GET /v1/exports/{id}untilsucceeded:download_urlis a signed link valid 12 h (it is re-signed on every GET); the file is kept 48 hours after the video was last saved.
Instead of polling you can get a webhook when each step ends. Polling is free, but every call counts toward your requests-per-minute limit, so poll every 20–30 seconds.
Your own voiceover
Ask for an upload URL, PUT your audio file there, then create the video with audio_key. Our server measures the file when it arrives (the PUT answers its duration_sec) and the video is billed on that length. Do the same with kind: "music" and music_key to use your own background track.
#!/usr/bin/env bash
# Your own voiceover (any common audio file) -> video
set -euo pipefail
API=https://vuesy-app-test.684108.xyz/v1
AUTH="Authorization: Bearer $VUESY_API_KEY"
FILE=${1:-voiceover.mp3}
# 1. Ask for an upload URL and PUT the file there (the URL is valid for 1 hour)
UP=$(curl --fail-with-body -sS -X POST "$API/uploads" -H "$AUTH" -H "Content-Type: application/json" -d '{"kind": "audio"}')
curl --fail-with-body -sS -X PUT --upload-file "$FILE" "$(echo "$UP" | jq -r .put_url)" | jq '{duration_sec}'
# 2. Create the video (billed on the length our server measured)
curl --fail-with-body -sS -X POST "$API/videos" -H "$AUTH" -H "Content-Type: application/json" \
-H "Idempotency-Key: voice-$(date +%s)" \
-d "{\"audio_key\": \"$(echo "$UP" | jq -r .key)\", \"title\": \"My narrated video\"}" | jq '{id, status, cost_credits}'
AI scripts & thumbnails
Scripts and thumbnails are written in the style of a YouTube channel you pick (channel_url, or up to 3 video_ids). With only a topic, we search YouTube for it and use 3 videos as references (same price; references_source: "topic_search"). They run in the background: POST returns 202 with an id; poll GET /v1/scripts/{id}.
# Write a script with AI in the style of a YouTube channel, then turn it into a video
import os, time, uuid
import requests
API = "https://vuesy-app-test.684108.xyz/v1"
H = {"Authorization": "Bearer " + os.environ["VUESY_API_KEY"]}
def ok(r):
if r.status_code >= 400:
raise SystemExit("API error %s: %s" % (r.status_code, r.json().get("error")))
return r.json()
task = ok(requests.post(API + "/scripts", headers=dict(H, **{"Idempotency-Key": str(uuid.uuid4())}), json={
"channel_url": "https://www.youtube.com/@NASA",
"topic": "How the James Webb telescope unfolded in space",
"minutes": 1,
}, timeout=60))
while task["status"] == "running":
time.sleep(10)
task = ok(requests.get(API + "/scripts/" + task["id"], headers=H, timeout=60))
if task["status"] != "succeeded":
raise SystemExit(task["error"])
print(task["result"]["title"], "-", task["result"]["words"], "words")
video = ok(requests.post(API + "/videos", headers=dict(H, **{"Idempotency-Key": str(uuid.uuid4())}),
json={"script": task["result"]["script"], "title": task["result"]["title"]}, timeout=60))
print("video", video["id"], video["status"])
n8n & Make templates
Ready-to-import workflows for topic in a Google Sheet → Vuesy video → download link back in the sheet.
Download the n8n workflow Download the Make blueprint
- n8n: Workflows → Import from file. Create a Header Auth credential named
Vuesy API keywith nameAuthorizationand valueBearer vuesy_..., and connect your Google Sheets credential. - Make: Create a new scenario → … → Import Blueprint. Put your key in the Authorization header of the HTTP modules and pick your sheet.
- Zapier: use Webhooks by Zapier with the same four calls (create, poll, export, poll).
The sheet needs columns topic, channel_url, script, title, status, video_id, download_url, error and (optional) voice — a voice id from GET /v1/voices.
Rows with a script go straight to a video; rows with only a topic get an AI script first, written in the style of the YouTube channel in channel_url (required for topic rows).
If a row fails, the template writes failed and the reason in error; clear status to try that row again.
Webhooks
Get a POST on your server when a video, export, script or thumbnail created by your key ends. Add an endpoint with
POST /v1/webhooks (up to 3 per key). The answer includes the signing secret: it is shown only there and on
rotate-secret.
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/webhooks" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Content-Type: application/json" -d '{"url": "https://yourshop.com/vuesy/webhook", "events": ["video.succeeded", "video.failed", "export.succeeded", "export.failed"]}'Events: video.succeeded, video.failed, video.cancelled, export.succeeded, export.failed, export.cancelled, script.succeeded, script.failed, thumbnail.succeeded, thumbnail.failed (default: all). Each POST body is an event whose data.object is the same object
GET /v1/videos/{id}, /v1/exports/{id}, /v1/scripts/{id} or /v1/thumbnails/{id} returns:
{
"id": "evt_3ccf2dbe8b15fc0d0ef3",
"object": "event",
"type": "export.succeeded",
"api_version": "2026-11-01",
"created_at": 1793633333,
"data": {
"object": {
"id": "69f87269-e12f-44a7-b33f-81e6a00ed6da",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 0,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793633282,
"updated_at": 1793633333,
"object": "export",
"download_filename": "music-check.mp4",
"download_url": "https://vuesy-app-test.684108.xyz/media/projects/78f0f51d-ac42-4d91-83af-12f7bc173f2a/exports/69f87269-e12f-44a7-b33f-81e6a00ed6da.mp4?exp=1793678351&name=music-check.mp4&sig=...",
"download_url_expires_in_s": 43200
}
}
}Verify the signature
Header Vuesy-Signature: t=<unix>,v1=<hex>, where v1 = HMAC-SHA256 of "<t>.<raw body>" with your secret.
Use the raw body bytes, compare in constant time and reject t older than 5 minutes. This exact code runs in our test suite.
import crypto from "node:crypto";
// rawBody: the request bytes exactly as received (e.g. express.raw({ type: "application/json" })).
// header: the Vuesy-Signature header.
export function verifyVuesy(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import hashlib, hmac, time
def verify_vuesy(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
"""raw_body: the request bytes exactly as received. header: Vuesy-Signature."""
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t = parts.get("t", "")
if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
- Answer 2xx within 10 s and do the work afterwards. Otherwise we retry at +30 s, 2 min, 10 min, 30 min, 2 h, 6 h and 12 h (8 attempts).
- Dedupe on the event id (
id, also inVuesy-Event-Id): retries keep the same id. Order is not guaranteed. - Webhooks are a hint:
GETis the truth, and polling keeps working as before. - 25 events in a row that use all attempts disable the endpoint (
status: "disabled"); turn it back on withPOST /v1/webhooks/{id}and{"enabled": true}. POST /v1/webhooks/{id}/testsends a signedwebhook.testnow;GET /v1/webhooks/{id}/deliveriesshows the last attempts;POST /v1/webhooks/{id}/deliveries/{evt}/retrysends one again.- URL:
https://on port 443 with a public host name. No redirects are followed.
Build on Vuesy / resell
Sell videos under your own brand. Your customer talks to your site; only your backend talks to Vuesy.
- Your site takes the order. Your backend (it holds the API key, never the browser) calls
POST /v1/videoswithexternal_id(your order id) andmetadata(up to 20 keys). Both come back on every object and webhook. - Wait for the
video.succeededwebhook, or pollGET /v1/videos/{id}. POST /v1/videos/{id}/exportwithdownload_filename(e.g."Acme Films - order 1001"): the MP4 downloads with your name, not ours.- On
export.succeeded, download the MP4 fromdownload_urland store it on your own storage. The link lasts 12 h and the file 48 hours: never give our link to your customer.
Find orders later with or GET /v1/videos?external_id=order-1001?metadata[shop]=acme (also on GET /v1/exports).
Page with starting_after = next_starting_after of the previous page.
Reseller credit pack
0.0125 USD per credit, minimum 20,000 credits (250 USD). A video costs the same credits as in the app: voice quality, length,
thumbnails and AI scripts change the total, so quote each order with GET /v1/quote before you set your price.
Buy it on the API & MCP page; the credits never expire.
Questions and bigger limits: support@vuesy.dev.
Good to know
- Music catalog (for
music_track="<mood>/<file>"):GET /v1/music. - Cancel works on queued or running jobs; on a finished one it returns the object unchanged. A cancel shows in
GET /v1/credits/historyas aholdand then arefundwith the samejob_id. - Upload with a plain
PUTof the file body toput_urlfrom your server (no extra headers). - Script and thumbnail tasks can't be cancelled; a failed one gives its credits back.
- Idempotency-Key is 8-64 characters: for longer ids (e.g. Stripe session ids) send a hash.
- Webhook signatures are over the RAW request body: in no-code tools, keep the raw body (don't re-serialise the JSON) before you check
Vuesy-Signature.
- Download vuesy-reseller-kit.zip.
- Put your API key in the server environment (
VUESY_API_KEY), never in the page. - Set your brand and price per credit in
kit.config.js. - Deploy it (Vercel, Netlify or Cloudflare) and add its webhook URL with
POST /v1/webhooks.
Credits per call
| Call | Credits |
|---|---|
| Create a video | Same price as the studio: by length, voice quality and language. Ask first with GET /v1/quote. Refunded if it fails or you cancel it. |
| Export to MP4 | First export of a video: free. Re-exports: 4. |
| AI script / thumbnail | 4 each, or free while your plan's monthly allowance lasts. |
| Everything else (status, lists, voices, quote, uploads, references) | Free |
Your own voiceover is billed on its real length, measured by our server. Using the API or MCP costs nothing extra.
Limits per plan
Generous by design: these are safety rails, not a paywall.
| Limit | Creator | Studio | Agency |
|---|---|---|---|
| Videos per day per key | 100 | 300 | 1,000 |
| API videos per day (all keys and apps) | 150 | 400 | 1,500 |
| Rendering at once (shared with the studio) | 5 | 10 | 20 |
| Active keys | 3 | 5 | 10 |
| Requests per minute per key | 60 | 60 | 120 |
| Requests per minute (all keys) | 120 | 180 | 300 |
| Paid requests per minute | 10 | 20 | 30 |
| Scripts and thumbnails at once | 1 | 2 | 3 |
When you hit one you get 429 with a Retry-After header. GET /v1/account shows your current limits and today's usage of the key.
Need higher limits? Email support@vuesy.dev with what you are building and the volume you need.
Idempotency
Never pay twice for the same request. Every call that spends credits (POST /v1/videos, /export, /v1/scripts, /v1/thumbnails)
needs an Idempotency-Key header: any unique string (8–64 letters, digits, - or _), e.g. a UUID or your own row id.
If a request times out, send it again with the same key and body: you get the same video back (200) instead of a new charge.
The same key with a different body is a 409. If the video or export made with that key failed or was cancelled, the same key makes a new one. If your own id is longer than 64 characters, send a hash of it.
Errors
Errors are JSON: {"error": "message", ...}. Newer errors also carry a stable code.
| Status | Meaning |
|---|---|
| 400 | Bad request: a field is missing or wrong, or the Idempotency-Key header is missing (idempotency_key_required). The message says which. |
| 401 | Missing, wrong or revoked API key (invalid_api_key). |
402 plan_required | Your plan doesn't include the API: it needs a paid plan. |
402 insufficient_credits | Not enough credits (required, available). Nothing was charged. |
403 insufficient_scope | The key doesn't have the scope this call needs (required says which). |
| 404 | Unknown id (or it belongs to another account). |
400 dns / blocked_address | Webhook URL: the host doesn't resolve, or resolves to a private address. |
409 idempotency_conflict | The Idempotency-Key was used with a different body. |
409 video_not_ready | The video isn't ready to export yet (status says where it is). |
409 webhook_limit | This key already has 3 webhooks. |
410 project_expired | The project expired (projects are kept 48 hours): create the video again. |
| 413 | Your audio is longer than 90 minutes. |
422 above_max_cost | The price (cost_credits) is above your max_cost_credits. Nothing charged. |
| 422 | Valid request we can't do, e.g. an audio or music file we can't read. |
429 rate_limited | Requests per minute (per key, per account, or paid calls). Wait Retry-After seconds. |
429 daily_credit_cap | The key's daily credit cap: this call would go over it. Wait Retry-After seconds. |
429 key_videos_per_day | This key reached its videos per day (resets 00:00 UTC). |
429 account_videos_per_day | Your account reached its API videos per day across all keys and apps (resets 00:00 UTC). |
429 too_many_rendering | Your account already has its plan's maximum of videos and exports rendering (shared with the studio). Nothing charged. |
429 too_many_tasks | Your account already has its plan's maximum of scripts and thumbnails in progress. Nothing charged. |
| 502 | Our video server or YouTube didn't answer (references). Nothing charged; retry in a minute. |
| 503 | An update or an outage: new videos are paused for a few minutes. Nothing charged. |
A topic-only script whose YouTube search finds nothing (or fails) ends as a failed task with error saying so, and its credits come back.
{
"error": "daily_credit_cap",
"max_credits_day": 170,
"used_today": 75,
"required": 102,
"retry_after": 41319
}
Endpoint reference
Base URL https://vuesy-app-test.684108.xyz. Machine-readable: /openapi.json (OpenAPI 3.1).
Videos
/v1/videosCreate a video. Charges the video price when accepted (refunded if it fails). Poll GET /v1/videos/{id}.
| Field | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
script | body | string · maxLength 50000 | The narration text (TTS voice). One of script, script_key, audio_key. |
script_key | body | string | Key from POST /v1/uploads/script (for long scripts). |
audio_key | body | string | Key from POST /v1/uploads with kind=audio: your own voiceover (no TTS). |
duration_sec | body | integer · minimum 1, maximum 5400 | Not needed: a script is priced by its length and your own audio by the length our server measured when you uploaded it. |
lang | body | string | Language code from GET /v1/languages, or auto. Default en (script) / auto (audio). |
voice | body | string | Voice id from GET /v1/voices (script videos only). |
title | body | string · maxLength 150 | |
subtitles | body | boolean · default true | |
animations | body | boolean · default true | |
background | body | string | blue (default), cinematic, vintage, light or grid |
no_music | body | boolean · default false | |
music_volume | body | number · minimum 0, maximum 1, default 0.03 | Background music volume; above 0.2 plays at 0.2. |
music_style | body | one of: epic, documentary, tension, sad, chill, upbeat | |
music_track | body | string | '<mood>/<file>.mp3' from GET /v1/music |
music_key | body | string | Key from POST /v1/uploads with kind=music: your own background track. |
max_cost_credits | body | integer · minimum 1, maximum 100000 | Refuse (422) if the video would cost more; never charged above it. Not with audio_key. |
options | body | object | Same fields nested (accepted for web-body compatibility). |
external_id | body | string | Your own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=. |
metadata | body | object | Your own data, echoed on the object and on webhooks: up to 20 keys [A-Za-z0-9_.-]{1,40}, string values up to 500 characters (numbers become strings), 8 KB in total. |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/videos" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"script": "In 1969, three astronauts left Earth...", "title": "Apollo 11", "voice": "vega:aurora", "background": "cinematic", "music_style": "documentary"}'
{
"id": "451feb91-e0cd-4f8c-8d8c-d8c20bd5c572",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 75,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793632872,
"updated_at": 1793633094,
"object": "video",
"estimated_duration_sec": 8
}
/v1/videosList videos (newest first)
| Field | In | Type | Description |
|---|---|---|---|
limit | query | integer · minimum 1, maximum 100, default 10 | |
status | query | one of: queued, running, succeeded, failed, cancelled, interrupted | |
starting_after | query | string | Cursor: the last id of the previous page (next_starting_after). |
external_id | query | string | Only objects created with this external_id. |
metadata | query | object | metadata[key]=value: only objects whose metadata has that value (up to 5 keys; metadata.key=value also works). |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/videos?limit=10&status=succeeded" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "list",
"data": [
{
"id": "451feb91-e0cd-4f8c-8d8c-d8c20bd5c572",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 75,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793632872,
"updated_at": 1793633094,
"object": "video",
"estimated_duration_sec": 8
},
{
"id": "b0abe4c5-e8ec-4e45-93fb-73753e85e3dd",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "1bd7287a-344c-4a8b-ad83-8bc3c40c34ff",
"cost_credits": 75,
"refunded": false,
"external_id": "vo-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793632865,
"updated_at": 1793633181,
"object": "video",
"estimated_duration_sec": 11
}
],
"has_more": true,
"next_starting_after": "b0abe4c5-e8ec-4e45-93fb-73753e85e3dd"
}
/v1/videos/{id}Get a video
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/videos/$VIDEO_ID" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "451feb91-e0cd-4f8c-8d8c-d8c20bd5c572",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 75,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793632872,
"updated_at": 1793633094,
"object": "video",
"estimated_duration_sec": 8
}
/v1/jobs/{id}/cancelCancel a video or export (credits come back). Works on a queued or running video or export; a finished one comes back unchanged.
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/jobs/$VIDEO_ID/cancel" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "451feb91-e0cd-4f8c-8d8c-d8c20bd5c572",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 75,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793632872,
"updated_at": 1793633094,
"object": "video",
"estimated_duration_sec": 8
}
Exports
/v1/videos/{id}/exportExport the video to MP4 (1080p)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
quality | body | one of: high, medium, fast · default "medium" | Accepted; every export is rendered at 1080p. |
download_filename | body | string · maxLength 200 | File name of the download (your brand, not Vuesy's); .mp4 is added. Default vuesy_video.mp4. |
external_id | body | string | Default: the video's. |
metadata | body | object | Default: the video's metadata. |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/videos/$VIDEO_ID/export" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"quality": "high", "download_filename": "Acme Films - order 1001"}'
{
"id": "69f87269-e12f-44a7-b33f-81e6a00ed6da",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 0,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793633282,
"updated_at": 1793633333,
"object": "export",
"download_filename": "music-check.mp4",
"download_url": "https://vuesy-app-test.684108.xyz/media/projects/78f0f51d-ac42-4d91-83af-12f7bc173f2a/exports/69f87269-e12f-44a7-b33f-81e6a00ed6da.mp4?exp=1793678351&name=music-check.mp4&sig=...",
"download_url_expires_in_s": 43200
}
/v1/exportsList exports (newest first)
| Field | In | Type | Description |
|---|---|---|---|
limit | query | integer · minimum 1, maximum 100, default 10 | |
starting_after | query | string | Cursor: the last id of the previous page (next_starting_after). |
external_id | query | string | Only objects created with this external_id. |
metadata | query | object | metadata[key]=value: only objects whose metadata has that value (up to 5 keys; metadata.key=value also works). |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/exports?limit=10&external_id=order-1001" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "list",
"data": [
{
"id": "69f87269-e12f-44a7-b33f-81e6a00ed6da",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 0,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793633282,
"updated_at": 1793633333,
"object": "export",
"download_filename": "music-check.mp4",
"download_url": "https://vuesy-app-test.684108.xyz/media/projects/78f0f51d-ac42-4d91-83af-12f7bc173f2a/exports/69f87269-e12f-44a7-b33f-81e6a00ed6da.mp4?exp=1793678352&name=music-check.mp4&sig=...",
"download_url_expires_in_s": 43200
},
{
"id": "370cb16a-ac3f-4381-9c3a-e6689d591e5e",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "1bd7287a-344c-4a8b-ad83-8bc3c40c34ff",
"cost_credits": 0,
"refunded": false,
"external_id": "vo-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793633190,
"updated_at": 1793633271,
"object": "export",
"download_filename": "voiceover-check.mp4",
"download_url": "https://vuesy-app-test.684108.xyz/media/projects/1bd7287a-344c-4a8b-ad83-8bc3c40c34ff/exports/370cb16a-ac3f-4381-9c3a-e6689d591e5e.mp4?exp=1793678352&name=voiceover-check.mp4&sig=...",
"download_url_expires_in_s": 43200
}
],
"has_more": true,
"next_starting_after": "370cb16a-ac3f-4381-9c3a-e6689d591e5e"
}
/v1/exports/{id}Get an export (download_url when succeeded)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/exports/$EXPORT_ID" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "69f87269-e12f-44a7-b33f-81e6a00ed6da",
"status": "succeeded",
"progress": {
"percent": 100,
"phase": "Completed",
"eta_s": 0
},
"queue_position": null,
"project_id": "78f0f51d-ac42-4d91-83af-12f7bc173f2a",
"cost_credits": 0,
"refunded": false,
"external_id": "mu-1793632856799",
"metadata": {},
"error": null,
"created_at": 1793633282,
"updated_at": 1793633333,
"object": "export",
"download_filename": "music-check.mp4",
"download_url": "https://vuesy-app-test.684108.xyz/media/projects/78f0f51d-ac42-4d91-83af-12f7bc173f2a/exports/69f87269-e12f-44a7-b33f-81e6a00ed6da.mp4?exp=1793678351&name=music-check.mp4&sig=...",
"download_url_expires_in_s": 43200
}
Uploads
/v1/uploads/scriptUpload a long script. Pass key as script_key to POST /v1/videos.
| Field | In | Type | Description |
|---|---|---|---|
text | body | string · required · maxLength 50000 |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/uploads/script" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Content-Type: application/json" -d '{"text": "In 1969, three astronauts left Earth on a journey nobody had made before..."}'
{
"key": "c0fd46f8b4d8408a93f2326562ee50f2-script.txt",
"chars": 153
}
/v1/uploadsGet a signed URL to upload your own voiceover or music. Then PUT the file bytes to put_url (any common audio format) within 1 hour, and pass key as audio_key or music_key to POST /v1/videos.
| Field | In | Type | Description |
|---|---|---|---|
kind | body | one of: audio, music · required |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/uploads" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Content-Type: application/json" -d '{"kind": "audio"}'
{
"object": "upload",
"kind": "audio",
"key": "bf26aa9ed673428b93e19ddaab0dc1d8-audio.mp3",
"put_url": "https://vuesy-app-test.684108.xyz/v1/uploads/put/bf26aa9ed673428b93e19ddaab0dc1d8-audio.mp3?u=RgZPSvAvQNvtUaUZNypgqB5JhH32iaSF&exp=1793638746&sig=...",
"method": "PUT",
"expires_in_s": 3600,
"max_bytes": 100663296
}
Voices
/v1/languagesVoice languages
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/languages" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"languages": [
{
"code": "en",
"name": "English",
"model": "vega",
"chars_per_min": 998,
"quality": "standard"
},
{
"code": "nl",
"name": "Dutch",
"model": "nova",
"chars_per_min": 774,
"quality": "high"
},
{
"code": "fr",
"name": "French",
"model": "nova",
"chars_per_min": 812,
"quality": "high"
}
]
}
/v1/voicesVoices of a language
| Field | In | Type | Description |
|---|---|---|---|
lang | query | string | Language code (default en). |
quality | query | one of: standard, high |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/voices?lang=en&quality=standard" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"lang": "en",
"voices": [
{
"id": "vega:aurora",
"name": "Aurora",
"gender": "female",
"age": null,
"style": "narration",
"region": "us",
"model": "vega",
"sample_url": null,
"quality": "standard"
},
{
"id": "vega:luna",
"name": "Luna",
"gender": "female",
"age": null,
"style": "narration",
"region": "us",
"model": "vega",
"sample_url": null,
"quality": "standard"
}
]
}
/v1/musicBackground music catalog by mood. For music_style ('<mood>') or music_track ('<mood>/<file>') on POST /v1/videos.
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/music" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"catalog": [
{
"mood": "epic",
"label": "Epic",
"tracks": [
{
"file": "epic-vista.mp3",
"title": "Epic Vista",
"artist": "John Bartmann",
"duration": 167.6
},
{
"file": "fantasy-orchestral.mp3",
"title": "Fantasy Orchestral Theme",
"artist": "Joth",
"duration": 191.7
}
]
},
{
"mood": "documentary",
"label": "Documentary",
"tracks": [
{
"file": "edge-of-the-sky.mp3",
"title": "Edge of the Sky",
"artist": "John Bartmann",
"duration": 244.1
},
{
"file": "gsf-discovery.mp3",
"title": "GSF Discovery",
"artist": "vitalezzz",
"duration": 245.4
}
]
}
]
}
Scripts & thumbnails
/v1/referencesVideos of a YouTube channel (references for scripts and thumbnails)
| Field | In | Type | Description |
|---|---|---|---|
channel_url | query | string · required | |
sort | query | one of: recent, popular |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/references?channel_url=https://www.youtube.com/@NASA" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "references",
"type": "channel",
"sort": "recent",
"videos": [
{
"id": "Zezt6EsG9WE",
"title": "NASA Moon Base: Rovers and Lunar Terrain Vehicles (October 2026 Update)",
"url": "https://www.youtube.com/watch?v=Zezt6EsG9WE",
"thumbnail_url": "https://i.ytimg.com/vi/Zezt6EsG9WE/hqdefault.jpg",
"duration_sec": 558,
"views": 996000
},
{
"id": "5b3n-u_0QAI",
"title": "Planetary Defenders: This Is Not an Exercise",
"url": "https://www.youtube.com/watch?v=5b3n-u_0QAI",
"thumbnail_url": "https://i.ytimg.com/vi/5b3n-u_0QAI/hqdefault.jpg",
"duration_sec": 1215,
"views": 677000
},
{
"id": "ErAqN6gXqZQ",
"title": "NASA's SpaceX Crew-13 Mission (Official NASA Trailer)",
"url": "https://www.youtube.com/watch?v=ErAqN6gXqZQ",
"thumbnail_url": "https://i.ytimg.com/vi/ErAqN6gXqZQ/hqdefault.jpg",
"duration_sec": 62,
"views": 106000
}
],
"credits": {
"script": 4,
"thumbnail": 4
},
"included_in_plan_left": {
"scripts": 11,
"thumbnails": 12
}
}
/v1/scriptsWrite a script with AI (async). Give channel_url, video_ids or only topic (then Vuesy finds 3 references on YouTube; if it finds none, the task fails with nothing charged).
| Field | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
channel_url | body | string · maxLength 500 | YouTube channel link; the first 3 videos are the references. Or pass video_ids. |
video_ids | body | array · maxItems 3 | |
reference_titles | body | array · maxItems 3 | |
topic | body | string · maxLength 400, minLength 8 | What the script is about. Alone (no channel_url / video_ids) Vuesy searches YouTube for the topic and uses 3 videos as style references (same price). |
minutes | body | integer · minimum 1, maximum 90, default 8 | |
lang | body | string | Force the script language (e.g. en, es). |
sort | body | one of: recent, popular · default "recent" | |
external_id | body | string | Your own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=. |
metadata | body | object | Your own data, echoed on the object and on webhooks: up to 20 keys [A-Za-z0-9_.-]{1,40}, string values up to 500 characters (numbers become strings), 8 KB in total. |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/scripts" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"channel_url": "https://www.youtube.com/@NASA", "topic": "How Webb unfolded in space", "minutes": 1}'
{
"id": "scr_f31e6d11c9fa4595",
"object": "script",
"status": "succeeded",
"result": {
"title": "Your microwave door is a Faraday cage. Here’s why it still lets light through",
"script": "Look at the mesh on your microwave door. It looks like a window. It is actually a wall. The holes are about a millimeter wide, and that size is not an accident. A microwave oven floods the chamber with electromagnetic wa…",
"words": 171
},
"error": null,
"external_id": null,
"metadata": {},
"references_source": "channel",
"created_at": 1793630997,
"updated_at": 1793631051
}
/v1/scripts/{id}Get a script task
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/scripts/$SCRIPT_ID" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "scr_f31e6d11c9fa4595",
"object": "script",
"status": "succeeded",
"result": {
"title": "Your microwave door is a Faraday cage. Here’s why it still lets light through",
"script": "Look at the mesh on your microwave door. It looks like a window. It is actually a wall. The holes are about a millimeter wide, and that size is not an accident. A microwave oven floods the chamber with electromagnetic wa…",
"words": 171
},
"error": null,
"external_id": null,
"metadata": {},
"references_source": "channel",
"created_at": 1793630997,
"updated_at": 1793631051
}
/v1/thumbnailsMake a thumbnail with AI (async)
| Field | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
title | body | string · required · maxLength 300 | |
text | body | string · maxLength 120 | |
subject | body | string · maxLength 300 | |
script | body | string · maxLength 50000 | |
with_text | body | boolean · default true | |
channel_url | body | string · maxLength 500 | YouTube channel link; the first 3 videos are the references. Or pass video_ids. |
video_ids | body | array · maxItems 3 | |
sort | body | one of: recent, popular · default "recent" | |
external_id | body | string | Your own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=. |
metadata | body | object | Your own data, echoed on the object and on webhooks: up to 20 keys [A-Za-z0-9_.-]{1,40}, string values up to 500 characters (numbers become strings), 8 KB in total. |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/thumbnails" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"title": "Webb unfolds", "video_ids": ["dQw4w9WgXcQ"]}'
{
"id": "thm_0ff6af5503c54e7d",
"object": "thumbnail",
"status": "succeeded",
"result": {
"image_url": "https://vuesy-app-test.684108.xyz/media/thumbnails/RgZPSvAvQNvtUaUZNypgqB5JhH32iaSF/c2a6065947bf4609a8a6cddf3879e263.jpg?exp=1794232721&sig=...",
"text": "BLUE SKY SECRET"
},
"error": null,
"external_id": null,
"metadata": {},
"created_at": 1793627901,
"updated_at": 1793627921
}
/v1/thumbnails/{id}Get a thumbnail task
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/thumbnails/$THUMBNAIL_ID" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "thm_0ff6af5503c54e7d",
"object": "thumbnail",
"status": "succeeded",
"result": {
"image_url": "https://vuesy-app-test.684108.xyz/media/thumbnails/RgZPSvAvQNvtUaUZNypgqB5JhH32iaSF/c2a6065947bf4609a8a6cddf3879e263.jpg?exp=1794232721&sig=...",
"text": "BLUE SKY SECRET"
},
"error": null,
"external_id": null,
"metadata": {},
"created_at": 1793627901,
"updated_at": 1793627921
}
Webhooks
/v1/webhooksAdd a webhook endpoint (max 3 per key). Events for videos, exports, scripts and thumbnails created by THIS key, sent when they end. The secret is shown only here and on rotate-secret.
| Field | In | Type | Description |
|---|---|---|---|
url | body | string · required · maxLength 2000 | https, port 443, a public host name (no IP, no localhost/.local/.internal); every address it resolves to must be public. 10 s timeout, no redirects. |
events | body | array | Default (or ["*"]): all events. |
description | body | string · maxLength 200 |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/webhooks" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Content-Type: application/json" -d '{"url": "https://yourshop.com/vuesy/webhook", "events": ["video.succeeded", "video.failed", "export.succeeded", "export.failed"]}'
{
"id": "wh_d7ed5d4fbbfb94c1",
"object": "webhook",
"url": "https://httpbin.org/post",
"events": [
"video.succeeded",
"video.failed",
"export.succeeded",
"export.failed"
],
"description": "orders",
"status": "enabled",
"disabled_reason": null,
"failed_in_a_row": 0,
"last_success_at": null,
"last_failure_at": null,
"created_at": 1793635158,
"secret": "whsec_..."
}
/v1/webhooksList this key's webhooks
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/webhooks" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "list",
"data": [
{
"id": "wh_d7ed5d4fbbfb94c1",
"object": "webhook",
"url": "https://httpbin.org/post",
"events": [
"video.succeeded",
"video.failed",
"export.succeeded",
"export.failed"
],
"description": "orders",
"status": "enabled",
"disabled_reason": null,
"failed_in_a_row": 0,
"last_success_at": null,
"last_failure_at": null,
"created_at": 1793635158
}
]
}
/v1/webhooks/{id}Get a webhook
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "wh_d7ed5d4fbbfb94c1",
"object": "webhook",
"url": "https://httpbin.org/post",
"events": [
"video.succeeded",
"video.failed",
"export.succeeded",
"export.failed"
],
"description": "orders",
"status": "enabled",
"disabled_reason": null,
"failed_in_a_row": 0,
"last_success_at": null,
"last_failure_at": null,
"created_at": 1793635158
}
/v1/webhooks/{id}Update a webhook (url, events, description, enabled)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
url | body | string · maxLength 2000 | https, port 443, a public host name (no IP, no localhost/.local/.internal); every address it resolves to must be public. 10 s timeout, no redirects. |
events | body | array | Default (or ["*"]): all events. |
description | body | string · maxLength 200 | |
enabled | body | boolean | true re-enables an endpoint disabled after failures. |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $VUESY_API_KEY" \
-H "Content-Type: application/json" -d '{"enabled": true}'
{
"id": "wh_d7ed5d4fbbfb94c1",
"object": "webhook",
"url": "https://httpbin.org/post",
"events": [
"video.succeeded",
"video.failed",
"export.succeeded",
"export.failed"
],
"description": "orders 2",
"status": "enabled",
"disabled_reason": null,
"failed_in_a_row": 0,
"last_success_at": null,
"last_failure_at": null,
"created_at": 1793635158
}
/v1/webhooks/{id}Delete a webhook
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X DELETE "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"deleted": true,
"id": "wh_d7ed5d4fbbfb94c1",
"object": "webhook"
}
/v1/webhooks/{id}/testSend one signed webhook.test event now
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID/test" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "webhook_test",
"event_id": "evt_3ccf2dbe8b15fc0d0ef3",
"delivered": true,
"status_code": 200,
"error": null
}
/v1/webhooks/{id}/rotate-secretNew signing secret (the old one stops at once)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID/rotate-secret" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "wh_d7ed5d4fbbfb94c1",
"object": "webhook",
"url": "https://httpbin.org/post",
"events": [
"video.succeeded",
"video.failed",
"export.succeeded",
"export.failed"
],
"description": "orders 2",
"status": "enabled",
"disabled_reason": null,
"failed_in_a_row": 0,
"last_success_at": 1793635164,
"last_failure_at": null,
"created_at": 1793635158,
"secret": "whsec_..."
}
/v1/webhooks/{id}/deliveriesRecent deliveries (newest first, kept 7 days)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
limit | query | integer · minimum 1, maximum 100 |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID/deliveries?limit=10" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "list",
"data": [
{
"id": "evt_3ccf2dbe8b15fc0d0ef3",
"object": "webhook_delivery",
"type": "webhook.test",
"object_id": null,
"status": "delivered",
"attempts": 1,
"last_status_code": 200,
"last_error": null,
"next_attempt_at": null,
"delivered_at": 1793635164,
"created_at": 1793635163
}
]
}
/v1/webhooks/{id}/deliveries/{evt}/retrySend an event again (same event id)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
evt | path | string · required |
curl --fail-with-body -sS -X POST "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID/deliveries/$EVENT_ID/retry" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"id": "evt_3ccf2dbe8b15fc0d0ef3",
"object": "webhook_delivery",
"status": "pending"
}
Account
/v1/accountYour plan, API limits and this key's usage today
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/account" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "account",
"plan": "creator",
"custom_limits": false,
"limits": {
"active_keys_per_account": 3,
"paid_requests_per_minute_per_account": 10,
"requests_per_minute_per_account": 120,
"requests_per_minute_per_key": 60,
"scripts_and_thumbnails_in_progress_per_account": 1,
"videos_in_progress_per_account": 5,
"videos_per_day_per_account": 150,
"videos_per_day_per_key": 100
},
"usage": {
"videos_today": 4,
"videos_in_progress": 0,
"resets_at": 1793664000
},
"key": {
"id": "1dVguFwtIGT5fWkIOBiudKhzjGkqusE7",
"scopes": [
"videos:write",
"videos:read",
"credits:read"
],
"max_credits_day": 1000,
"videos_today": 3,
"credits_spent_today": 225,
"videos_in_progress": 0
}
}
/v1/creditsCredit balance
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/credits" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"balance": 2850,
"held": 0,
"available": 2850,
"expires_at": 0,
"topup_credits": 2850,
"renews_at": 1796222309,
"key": {
"id": "1dVguFwtIGT5fWkIOBiudKhzjGkqusE7",
"max_credits_day": 1000,
"spent_today": 225
}
}
/v1/credits/historyCredit movements (newest first)
| Field | In | Type | Description |
|---|---|---|---|
limit | query | integer · minimum 1, maximum 100, default 50 |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/credits/history?limit=10" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"object": "list",
"data": [
{
"at": 1793632872,
"type": "hold",
"credits": -75,
"balance_after": 2850,
"job_id": "kcfc63440106ff521d1d4dfc0c6efc04b"
},
{
"at": 1793632865,
"type": "hold",
"credits": -75,
"balance_after": 2925,
"job_id": "k72cb1613a873f5fde24e73044e027889"
},
{
"at": 1793630313,
"type": "credit",
"credits": 150,
"balance_after": 3000,
"job_id": null
}
]
}
/v1/quotePrice of a video in credits
| Field | In | Type | Description |
|---|---|---|---|
chars | query | integer | Script length in characters (or pass duration_sec). |
duration_sec | query | integer | Length in seconds (for your own audio). |
lang | query | string | |
voice | query | string |
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/quote?chars=1200&lang=en" \
-H "Authorization: Bearer $VUESY_API_KEY"
{
"duration_sec": 80,
"lang": "en",
"voice": null,
"quality": "standard",
"cost_credits": 75
}
OpenAPI
OpenAPI 3.1, API version 1.0.0. Import it in Postman, Insomnia or your code generator.