Public API v1
Veedio Public API v1
Everything the web app does, available to a program: the same script writer, the same pipeline, the same credit deposit and the same refund on failure. Six endpoints, one of which makes videos.
- https://www.veedio.co/api/v1
- Authorization: Bearer vd_live_…
- 60 req/min
Authentication
Every request carries an API key. Create one in Settings → API keys; the plaintext is shown once and only its SHA-256 hash is stored, so there is no way to recover it later — mint a new one and revoke the old. A key looks like vd_live_ followed by 43 characters of base64url.
Four headers are accepted, so a script written for another vendor's API usually works with a one-line change.
Headers
-H "Authorization: Bearer vd_live_..." # preferred
-H "x-api-key: vd_live_..."
-H "x-veedio-api-key: vd_live_..."
-H "key: vd_live_..."A missing, malformed, unknown or revoked key all answer the same way — 401 with a WWW-Authenticate: Bearer header — because saying which would tell someone whether a guessed key exists.
A key is an identity, not a scope. It can create videos, spend credits and publish to connected accounts on the owner's behalf. Treat it like a password: environment variable, never a repository, never a browser. Keys themselves are managed from the app with a session, never with a key — a leaked credential must not be able to mint its replacement.
Errors
Every failure has the same body. Branch on code; show message. The message may be reworded, the code will not.
Error body
{
"error": {
"code": "insufficient_credits",
"message": "You are out of credits. Upgrade your plan to keep generating."
}
}| Status | code | Means |
|---|---|---|
| 400 | invalid_request | The body failed validation. The message names the field. |
| 401 | unauthorized | No key, or a key that is unknown or revoked. |
| 402 | insufficient_credits | Not enough credits for the deposit. Nothing was charged. |
| 404 | not_found | No such video — or it is not yours. |
| 409 | conflict | The video is not ready, has no rendered MP4 yet, or is already live on that platform. |
| 429 | rate_limited | Too many requests. See Retry-After. |
| 500 | internal_error | Ours. Retrying is safe. |
Rate limits
Per API key, sliding window: 60 requests a minute and 1,000 an hour. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 carries Retry-After in seconds.
A refused request is not counted, so backing off works rather than extending the penalty. These bound chatter, not spend — the real limit on anything expensive is your credit balance.
Credits
A generation costs 3 credits plus a charge per scene that follows what fills it: 2 for a still or stock footage, 13 for a scene the video model sets in motion. A 5-scene short is 13 credits with stills and 68 animated.
The real cost is not knowable when you ask — the script does not exist yet — so a deposit is charged before the job is queued and settled the moment the scenes are written. creditsEstimated on the create response is the deposit; creditsSpent on the video is what was actually charged. A generation that fails and is finally abandoned is refunded in full. Publishing is free. Regenerating a single scene is free.
Endpoints
Six endpoints
POST/api/v1/videos
Start a generation. Answers 202 Accepted, not 201: the row exists, the video does not yet. Poll GET /api/v1/videos/{id} until status is ready or failed — a few minutes.
Body
- workflowstringRequired
- The discriminator. It decides what source must contain and how much of the writing we do.
- prompt-to-videoA brief. We write the script, generate the visuals and cut it.
- script-to-videoYour script, narrated close to verbatim. You control every sentence.
- idea-to-videoA bare idea, expanded into an angle and then a script.
- article-to-videoA link, read and turned into a short: an article, a video, a post, a thread or a PDF.
- source.textstringRequired
- Up to 5,000 characters. The brief, the idea or the script, depending on the workflow. Required for every workflow except article-to-video.
- source.urlstring
- Up to 2,000 characters, http(s). Required for article-to-video. The page is fetched and read — an article, a YouTube transcript, a post on X, a Reddit thread, a LinkedIn post or a PDF — and the script is held to what it says.
- titlestringdefault Untitled video
- Up to 120 characters. Shown in the app, and used as the caption when you publish without one.
- presetstring
- A tool page's slug — `top-5-video-generator`, `storytime-video-generator`, `explainer-video-generator` and 50 others at https://www.veedio.co/tools. The page's format brief is put in front of your source before the script is written, so the video comes out in that shape. The brief itself is resolved here from the slug and is never accepted as text; an unknown slug answers 400. Each page's own brief is quoted verbatim at https://www.veedio.co/tools/{slug}/llms.txt.
- voicestringdefault rachel
- The narrator. Scene length is this voice's real duration, not an estimate. All five speak every one of the 29 languages — set `language` and the same voice reads it.
- rachelWarm, conversational
- adamDeep, documentary
- bellaBright, energetic
- joshCasual, young male
- elliSoft, storytelling
- captionStylestringdefault bold-center
- Captions are word-timed from the voice model's own character alignment and burned into the MP4. `none` renders the video without them.
- bold-centerBig white text in the lower third
- highlightWord-by-word yellow highlight, lower third
- subtleSmall caption near the bottom
- noneNo captions
- musicTrackstringdefault uplifting
- A licensed track, ducked under the narration and loudness-normalised with it. `none` leaves the voice alone.
- noneSilence
- upliftingBright, motivational
- lofiCalm, chill
- cinematicTense, epic
- trapPunchy, modern
- visualStylestringdefault cinematic
- Appended to the image prompt for every scene, so the whole video holds one look. Applies to generated frames; see mediaType for stock footage.
- cinematicCinematic
- 3d3D render
- animeAnime
- documentaryDocumentary
- retroRetro
- toyToy bricks
- voxelVoxel world
- clayClaymation
- comicComic
- watercolorWatercolor
- aspectRatiostringdefault 9:16
- The render is 1080-class in every ratio, with no watermark on any plan.
- 9:16Vertical — TikTok, Reels, Shorts
- 1:1Square — Feed posts
- 16:9Landscape — YouTube
Request
Response
202 Accepted
{
"id": "8f1c0f2e-8a4d-4f2b-9e2c-2f1a0b7c9d31",
"status": "queued",
"creditsEstimated": 68,
"url": "https://www.veedio.co/videos/8f1c0f2e-8a4d-4f2b-9e2c-2f1a0b7c9d31"
}Status codes
- 202queued
- 400invalid_request
- 401unauthorized
- 402insufficient_credits
- 429rate_limited
One endpoint, many workflows: new ones arrive as new workflow values rather than as new URLs, so a client written against this shape keeps working. Each workflow has its own page — start with prompt-to-video.
GET/api/v1/videos/{id}
Status and scenes. Scenes fill in as they are produced, so a client can show progress rather than a spinner.
Request
Response
200 OK
{
"id": "8f1c0f2e-8a4d-4f2b-9e2c-2f1a0b7c9d31",
"status": "ready",
"title": "Why most indie launches fail",
"workflow": "prompt-to-video",
"createdAt": "2026-09-06T09:12:44.000Z",
"videoUrl": "https://.../final.mp4",
"thumbnailUrl": "https://.../thumb.jpg",
"durationSeconds": 31.4,
"creditsSpent": 12,
"error": null,
"url": "https://www.veedio.co/videos/8f1c0f2e-8a4d-4f2b-9e2c-2f1a0b7c9d31",
"scenes": [
{
"position": 0,
"status": "ready",
"text": "Most indie founders launch to nobody.",
"imageUrl": "https://.../scene-0.png",
"clipUrl": null,
"audioUrl": "https://.../scene-0.mp3",
"durationSeconds": 4.2
}
]
}status is one of queued, processing, ready, failed. videoUrl is null until the render finishes; error says why when the status is failed. Poll no more than once every few seconds — generation takes minutes and the rate limit is per key.
- 200ok
- 401unauthorized
- 404not_found
GET/api/v1/videos
Your videos, newest first.
Query
- limitintegerdefault 20
- How many to return. Caps at 100. Newest first.
Request
cURL
curl "https://www.veedio.co/api/v1/videos?limit=10" \
-H "Authorization: Bearer $VEEDIO_API_KEY"Response
200 OK
{
"videos": [
{
"id": "8f1c0f2e-...",
"status": "ready",
"title": "Why most indie launches fail",
"url": "https://www.veedio.co/videos/8f1c0f2e-..."
}
]
}POST/api/v1/videos/{id}/publish
Publish a finished video to connected accounts. 202: each platform gets its own upload job, so a TikTok rejection does not hold up YouTube.
Body
- platformsstring[]Required
- Where to publish. At least one.
- tiktokTikTok
- instagramInstagram Reels
- youtubeYouTube Shorts
- captionstring
- Applies to every platform. Defaults to the video's title. Up to 2,200 characters.
- forcebooleandefault false
- Publish again to a platform this video is already live on. Leave it off when retrying a call you are unsure landed — a second post is visible to the creator's audience.
Request
Response
202 Accepted
{
"publications": [
{
"platform": "tiktok",
"status": "pending",
"url": null
}
]
}Refusals
- The video must be ready and have a rendered MP4 — otherwise 409.
- The platform must already be connected in Settings — otherwise 400, naming it.
- The video must not already be live on that platform — otherwise 409.
That last one is what makes this call safe to retry: an agent that is not sure its request landed can send it again without putting a second copy on the creator's account. Publishing costs no credits.
POST/api/v1/credits/estimate
Quote a generation, and read the balance, without spending anything. The endpoint ignores fields it does not use, so you can send the same body you would send to POST /api/v1/videos.
Body
- sceneCountinteger
- Without it you get the default quote, which is what the deposit assumes — the response says so with "estimated": true.
Request
Response
200 OK
{
"credits": 81,
"sceneCount": 6,
"estimated": false,
"breakdown": {
"base": 3,
"perScene": 13
},
"clipSeconds": null,
"upperBound": false,
"balance": 188
}GET/api/v1/me
Whose key this is, their plan and their balance. The cheapest way to check a key works.
Request
cURL
curl https://www.veedio.co/api/v1/me -H "Authorization: Bearer $VEEDIO_API_KEY"Response
200 OK
{
"email": "creator@example.com",
"plan": "pro",
"planName": "Pro",
"credits": 188,
"creditsRenewAt": "2026-10-01T00:00:00.000Z"
}A complete flow
Quote, create, wait, publish. The CLI is these four steps behind one command.
bash
export VEEDIO_API_KEY=vd_live_...
# 1. What will it cost?
curl -s -X POST https://www.veedio.co/api/v1/credits/estimate \
-H "Authorization: Bearer $VEEDIO_API_KEY" | jq .credits
# 2. Start it.
id=$(curl -s -X POST https://www.veedio.co/api/v1/videos \
-H "Authorization: Bearer $VEEDIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"workflow":"prompt-to-video","source":{"text":"why indie launches fail"}}' \
| jq -r .id)
# 3. Wait for it.
until [ "$(curl -s https://www.veedio.co/api/v1/videos/$id \
-H "Authorization: Bearer $VEEDIO_API_KEY" | jq -r .status)" = "ready" ]; do
sleep 10
done
# 4. Ship it.
curl -s -X POST https://www.veedio.co/api/v1/videos/$id/publish \
-H "Authorization: Bearer $VEEDIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platforms":["tiktok"]}'Or: veedio create --prompt "…" --wait --json — see the CLI reference.
Before you build, read what this API cannot do: no file posted up the wire (text and URLs only), no face swap or voice cloning, nothing longer than three minutes, no 4K, and no completion webhook yet.