Veedio

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."
  }
}
Error codes returned by the Veedio API
StatuscodeMeans
400invalid_requestThe body failed validation. The message names the field.
401unauthorizedNo key, or a key that is unknown or revoked.
402insufficient_creditsNot enough credits for the deposit. Nothing was charged.
404not_foundNo such video — or it is not yours.
409conflictThe video is not ready, has no rendered MP4 yet, or is already live on that platform.
429rate_limitedToo many requests. See Retry-After.
500internal_errorOurs. 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

Show this example as

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

Show this example as

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

Show this example as

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

Show this example as

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.