Get your API key
API & MCP

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.

  1. 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
  2. Put it in your environment
    export VUESY_API_KEY="vuesy_..."
  3. 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)"
    

    Needs jq.

    # 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"])
    

    pip install requests

    // 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);
    

    Node.js 18+, save as quickstart.mjs.

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.

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:

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

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.

How a video is made

  1. POST /v1/videos with your script (or audio_key). The price is reserved from your credits; you get an id.
  2. Poll GET /v1/videos/{id} every 20–30 s. status: queued → running (with progress.percent) → succeeded, or failed/cancelled (credits come back: refunded: true). A 1-minute video usually takes a few minutes.
  3. POST /v1/videos/{id}/export renders the 1080p MP4 (the first export of each video is free).
  4. Poll GET /v1/exports/{id} until succeeded: download_url is 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

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", ""))

Build on Vuesy / resell

Sell videos under your own brand. Your customer talks to your site; only your backend talks to Vuesy.

  1. Your site takes the order. Your backend (it holds the API key, never the browser) calls POST /v1/videos with external_id (your order id) and metadata (up to 20 keys). Both come back on every object and webhook.
  2. Wait for the video.succeeded webhook, or poll GET /v1/videos/{id}.
  3. POST /v1/videos/{id}/export with download_filename (e.g. "Acme Films - order 1001"): the MP4 downloads with your name, not ours.
  4. On export.succeeded, download the MP4 from download_url and 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 GET /v1/videos?external_id=order-1001 or ?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

Starter kit — a ready white-label site (order form, status page, download) you deploy yourself.
  1. Download vuesy-reseller-kit.zip.
  2. Put your API key in the server environment (VUESY_API_KEY), never in the page.
  3. Set your brand and price per credit in kit.config.js.
  4. Deploy it (Vercel, Netlify or Cloudflare) and add its webhook URL with POST /v1/webhooks.

Download the starter kit

Credits per call

CallCredits
Create a videoSame 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 MP4First export of a video: free. Re-exports: 4.
AI script / thumbnail4 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.

LimitCreatorStudioAgency
Videos per day per key1003001,000
API videos per day (all keys and apps)1504001,500
Rendering at once (shared with the studio)51020
Active keys3510
Requests per minute per key6060120
Requests per minute (all keys)120180300
Paid requests per minute102030
Scripts and thumbnails at once123

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.

StatusMeaning
400Bad request: a field is missing or wrong, or the Idempotency-Key header is missing (idempotency_key_required). The message says which.
401Missing, wrong or revoked API key (invalid_api_key).
402 plan_requiredYour plan doesn't include the API: it needs a paid plan.
402 insufficient_creditsNot enough credits (required, available). Nothing was charged.
403 insufficient_scopeThe key doesn't have the scope this call needs (required says which).
404Unknown id (or it belongs to another account).
400 dns / blocked_addressWebhook URL: the host doesn't resolve, or resolves to a private address.
409 idempotency_conflictThe Idempotency-Key was used with a different body.
409 video_not_readyThe video isn't ready to export yet (status says where it is).
409 webhook_limitThis key already has 3 webhooks.
410 project_expiredThe project expired (projects are kept 48 hours): create the video again.
413Your audio is longer than 90 minutes.
422 above_max_costThe price (cost_credits) is above your max_cost_credits. Nothing charged.
422Valid request we can't do, e.g. an audio or music file we can't read.
429 rate_limitedRequests per minute (per key, per account, or paid calls). Wait Retry-After seconds.
429 daily_credit_capThe key's daily credit cap: this call would go over it. Wait Retry-After seconds.
429 key_videos_per_dayThis key reached its videos per day (resets 00:00 UTC).
429 account_videos_per_dayYour account reached its API videos per day across all keys and apps (resets 00:00 UTC).
429 too_many_renderingYour account already has its plan's maximum of videos and exports rendering (shared with the studio). Nothing charged.
429 too_many_tasksYour account already has its plan's maximum of scripts and thumbnails in progress. Nothing charged.
502Our video server or YouTube didn't answer (references). Nothing charged; retry in a minute.
503An 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

POST/v1/videos

Create a video. Charges the video price when accepted (refunded if it fails). Poll GET /v1/videos/{id}.

Scope: videos:write · Credits: video price (GET /v1/quote)

FieldInTypeDescription
Idempotency-Keyheaderstring · requiredAny unique string per new request. Retrying with the same key and body returns the same object (never charges twice).
scriptbodystring · maxLength 50000The narration text (TTS voice). One of script, script_key, audio_key.
script_keybodystringKey from POST /v1/uploads/script (for long scripts).
audio_keybodystringKey from POST /v1/uploads with kind=audio: your own voiceover (no TTS).
duration_secbodyinteger · minimum 1, maximum 5400Not needed: a script is priced by its length and your own audio by the length our server measured when you uploaded it.
langbodystringLanguage code from GET /v1/languages, or auto. Default en (script) / auto (audio).
voicebodystringVoice id from GET /v1/voices (script videos only).
titlebodystring · maxLength 150
subtitlesbodyboolean · default true
animationsbodyboolean · default true
backgroundbodystringblue (default), cinematic, vintage, light or grid
no_musicbodyboolean · default false
music_volumebodynumber · minimum 0, maximum 1, default 0.03Background music volume; above 0.2 plays at 0.2.
music_stylebodyone of: epic, documentary, tension, sad, chill, upbeat
music_trackbodystring'<mood>/<file>.mp3' from GET /v1/music
music_keybodystringKey from POST /v1/uploads with kind=music: your own background track.
max_cost_creditsbodyinteger · minimum 1, maximum 100000Refuse (422) if the video would cost more; never charged above it. Not with audio_key.
optionsbodyobjectSame fields nested (accepted for web-body compatibility).
external_idbodystringYour own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=.
metadatabodyobjectYour 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"}'

Response 201 (a real object from our test server, read back with GET):

{
  "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
}
GET/v1/videos

List videos (newest first)

Scope: videos:read · Credits: free

FieldInTypeDescription
limitqueryinteger · minimum 1, maximum 100, default 10
statusqueryone of: queued, running, succeeded, failed, cancelled, interrupted
starting_afterquerystringCursor: the last id of the previous page (next_starting_after).
external_idquerystringOnly objects created with this external_id.
metadataqueryobjectmetadata[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"

Response 200 (real response from our test server):

{
  "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"
}
GET/v1/videos/{id}

Get a video

Scope: videos:read · Credits: free

FieldInTypeDescription
idpathstring · required
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/videos/$VIDEO_ID" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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
}
POST/v1/jobs/{id}/cancel

Cancel a video or export (credits come back). Works on a queued or running video or export; a finished one comes back unchanged.

Scope: videos:write · Credits: free

FieldInTypeDescription
idpathstring · 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"

Response 200 (a finished video comes back unchanged):

{
  "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

POST/v1/videos/{id}/export

Export the video to MP4 (1080p)

Scope: videos:write · Credits: first export free, then 4

FieldInTypeDescription
idpathstring · required
Idempotency-Keyheaderstring · requiredAny unique string per new request. Retrying with the same key and body returns the same object (never charges twice).
qualitybodyone of: high, medium, fast · default "medium"Accepted; every export is rendered at 1080p.
download_filenamebodystring · maxLength 200File name of the download (your brand, not Vuesy's); .mp4 is added. Default vuesy_video.mp4.
external_idbodystringDefault: the video's.
metadatabodyobjectDefault: 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"}'

Response 201 (a real object from our test server, read back with GET):

{
  "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
}
GET/v1/exports

List exports (newest first)

Scope: videos:read · Credits: free

FieldInTypeDescription
limitqueryinteger · minimum 1, maximum 100, default 10
starting_afterquerystringCursor: the last id of the previous page (next_starting_after).
external_idquerystringOnly objects created with this external_id.
metadataqueryobjectmetadata[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"

Response 200 (real response from our test server):

{
  "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"
}
GET/v1/exports/{id}

Get an export (download_url when succeeded)

Scope: videos:read · Credits: free

FieldInTypeDescription
idpathstring · required
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/exports/$EXPORT_ID" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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

POST/v1/uploads/script

Upload a long script. Pass key as script_key to POST /v1/videos.

Scope: videos:write · Credits: free

FieldInTypeDescription
textbodystring · 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..."}'

Response 201 (real response from our test server):

{
  "key": "c0fd46f8b4d8408a93f2326562ee50f2-script.txt",
  "chars": 153
}
POST/v1/uploads

Get 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.

Scope: videos:write · Credits: free

FieldInTypeDescription
kindbodyone 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"}'

Response 201 (real response from our test server):

{
  "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

GET/v1/languages

Voice languages

Scope: any key · Credits: free

curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/languages" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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"
    }
  ]
}
GET/v1/voices

Voices of a language

Scope: any key · Credits: free

FieldInTypeDescription
langquerystringLanguage code (default en).
qualityqueryone 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"

Response 200 (real response from our test server):

{
  "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"
    }
  ]
}
GET/v1/music

Background music catalog by mood. For music_style ('<mood>') or music_track ('<mood>/<file>') on POST /v1/videos.

Scope: any key · Credits: free

curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/music" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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

GET/v1/references

Videos of a YouTube channel (references for scripts and thumbnails)

Scope: scripts:write or thumbnails:write · Credits: free

FieldInTypeDescription
channel_urlquerystring · required
sortqueryone 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"

Response 200 (real response from our test server):

{
  "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
  }
}
POST/v1/scripts

Write 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).

Scope: scripts:write · Credits: 4 (or included in plan)

FieldInTypeDescription
Idempotency-Keyheaderstring · requiredAny unique string per new request. Retrying with the same key and body returns the same object (never charges twice).
channel_urlbodystring · maxLength 500YouTube channel link; the first 3 videos are the references. Or pass video_ids.
video_idsbodyarray · maxItems 3
reference_titlesbodyarray · maxItems 3
topicbodystring · maxLength 400, minLength 8What 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).
minutesbodyinteger · minimum 1, maximum 90, default 8
langbodystringForce the script language (e.g. en, es).
sortbodyone of: recent, popular · default "recent"
external_idbodystringYour own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=.
metadatabodyobjectYour 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}'

Response 202 (a real object from our test server, read back with GET):

{
  "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
}
GET/v1/scripts/{id}

Get a script task

Scope: scripts:write · Credits: free

FieldInTypeDescription
idpathstring · required
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/scripts/$SCRIPT_ID" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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
}
POST/v1/thumbnails

Make a thumbnail with AI (async)

Scope: thumbnails:write · Credits: 4 (or included in plan)

FieldInTypeDescription
Idempotency-Keyheaderstring · requiredAny unique string per new request. Retrying with the same key and body returns the same object (never charges twice).
titlebodystring · required · maxLength 300
textbodystring · maxLength 120
subjectbodystring · maxLength 300
scriptbodystring · maxLength 50000
with_textbodyboolean · default true
channel_urlbodystring · maxLength 500YouTube channel link; the first 3 videos are the references. Or pass video_ids.
video_idsbodyarray · maxItems 3
sortbodyone of: recent, popular · default "recent"
external_idbodystringYour own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=.
metadatabodyobjectYour 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"]}'

Response 202 (a real object from our test server, read back with GET):

{
  "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
}
GET/v1/thumbnails/{id}

Get a thumbnail task

Scope: thumbnails:write · Credits: free

FieldInTypeDescription
idpathstring · required
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/thumbnails/$THUMBNAIL_ID" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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

POST/v1/webhooks

Add 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.

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
urlbodystring · required · maxLength 2000https, 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.
eventsbodyarrayDefault (or ["*"]): all events.
descriptionbodystring · 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"]}'

Response 201 (real response from our test server):

{
  "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_..."
}
GET/v1/webhooks

List this key's webhooks

Scope: videos:read or videos:write · Credits: free

curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/webhooks" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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
    }
  ]
}
GET/v1/webhooks/{id}

Get a webhook

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · required
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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
}
POST/v1/webhooks/{id}

Update a webhook (url, events, description, enabled)

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · required
urlbodystring · maxLength 2000https, 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.
eventsbodyarrayDefault (or ["*"]): all events.
descriptionbodystring · maxLength 200
enabledbodybooleantrue 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}'

Response 200 (real response from our test server):

{
  "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
}
DELETE/v1/webhooks/{id}

Delete a webhook

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · required
curl --fail-with-body -sS -X DELETE "https://vuesy-app-test.684108.xyz/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "deleted": true,
  "id": "wh_d7ed5d4fbbfb94c1",
  "object": "webhook"
}
POST/v1/webhooks/{id}/test

Send one signed webhook.test event now

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · 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"

Response 200 (real response from our test server):

{
  "object": "webhook_test",
  "event_id": "evt_3ccf2dbe8b15fc0d0ef3",
  "delivered": true,
  "status_code": 200,
  "error": null
}
POST/v1/webhooks/{id}/rotate-secret

New signing secret (the old one stops at once)

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · 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"

Response 200 (real response from our test server):

{
  "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_..."
}
GET/v1/webhooks/{id}/deliveries

Recent deliveries (newest first, kept 7 days)

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · required
limitqueryinteger · 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"

Response 200 (real response from our test server):

{
  "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
    }
  ]
}
POST/v1/webhooks/{id}/deliveries/{evt}/retry

Send an event again (same event id)

Scope: videos:read or videos:write · Credits: free

FieldInTypeDescription
idpathstring · required
evtpathstring · 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"

Response 200 (real response from our test server):

{
  "id": "evt_3ccf2dbe8b15fc0d0ef3",
  "object": "webhook_delivery",
  "status": "pending"
}

Account

GET/v1/account

Your plan, API limits and this key's usage today

Scope: any key · Credits: free

curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/account" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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
  }
}
GET/v1/credits

Credit balance

Scope: credits:read · Credits: free

curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/credits" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "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
  }
}
GET/v1/credits/history

Credit movements (newest first)

Scope: credits:read · Credits: free

FieldInTypeDescription
limitqueryinteger · 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"

Response 200 (real response from our test server):

{
  "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
    }
  ]
}
GET/v1/quote

Price of a video in credits

Scope: any key · Credits: free

FieldInTypeDescription
charsqueryintegerScript length in characters (or pass duration_sec).
duration_secqueryintegerLength in seconds (for your own audio).
langquerystring
voicequerystring
curl --fail-with-body -sS "https://vuesy-app-test.684108.xyz/v1/quote?chars=1200&lang=en" \
  -H "Authorization: Bearer $VUESY_API_KEY"

Response 200 (real response from our test server):

{
  "duration_sec": 80,
  "lang": "en",
  "voice": null,
  "quality": "standard",
  "cost_credits": 75
}

OpenAPI

Download openapi.json

OpenAPI 3.1, API version 1.0.0. Import it in Postman, Insomnia or your code generator.