API
On this section

Guide

Getting started with the Python SDK

Install the SDK, submit your first job, and poll it to completion — the submit-and-poll loop every VEED model shares.

Every VEED model works the same way: you submit a job, the API accepts it immediately, and you poll until it is done. This guide walks that loop end to end in Python, using Fabric 1.0 as the example — swap the client attribute and the inputs and the shape is identical for every other model.

Install

The SDK supports Python 3.9 and later.

terminal
pip install veed

The client reads VEED_API_KEY from the environment, so export the workspace key you created in the dashboard:

terminal
export VEED_API_KEY="vp_..."

The whole loop

example.py
from veed import Veed

client = Veed()  # reads VEED_API_KEY

job = client.fabric.generate(
    audio_url="https://static-assets.veed.io/api-examples/fabric-input.mp3",
    image_url="https://static-assets.veed.io/api-examples/fabric-input.png",
    resolution="720p",
)
print(job.result)

That is the entire integration. generate() submits the job and polls until it finishes, every 10 seconds for this model, the interval the API advertises. A rate-limited poll waits for Retry-After instead, so you do not write a retry loop by hand. Pass poll_interval= or timeout= to change either.

Construct the client once and reuse it: it is safe to share across threads.

What this model takes

image_urlurlrequired

URL of the source image to animate.

Must be an http(s) URL.
audio_urlurlrequired

URL of the audio track to lip-sync to.

Must be an http(s) URL.
resolutionenumrequired

Output video resolution.

Accepts720p480p

Each field is a keyword argument of the same name. Both URLs must be publicly reachable over http(s) — the renderer fetches them itself, so a signed URL is fine but a localhost address is not.

resolution accepts 720p, 480p. It is required, and the reference and playground both show 720p as the example, which is the resolution most callers want.

Submitting and polling by hand

If you would rather drive the two calls yourself — a queue worker that persists the job id across restarts, say — they are the two endpoints generate() wraps:

POST
/v1/fabric-1.0
example.py
from veed import Veed

client = Veed()  # reads VEED_API_KEY

job = client.fabric.submit(
    audio_url="https://static-assets.veed.io/api-examples/fabric-input.mp3",
    image_url="https://static-assets.veed.io/api-examples/fabric-input.png",
    resolution="720p",
)
print(job.status)
GET
/v1/fabric-1.0/{job_id}
example.py
from veed import Veed

client = Veed()  # reads VEED_API_KEY

job = client.fabric.get("<job_id>")
print(job.status)

To resume waiting on a job you submitted earlier, call wait() with its id. It polls the way generate() does:

example.py
job = client.fabric.wait(job_id, timeout=1800)

The job envelope is the same either way:

credits_chargedintegeroptional

Credits charged for the job. Present once the job is COMPLETED and the final charge is known.

credits_estimatedintegeroptional

Credits quoted before the job ran. Present only on the response that created the job. This is an estimate, not the price: the charged amount can be higher or lower.

errorobjectoptionalJobErrorFabricErrorCode ↗

Failure information. Present once the job has FAILED.

Show 3 nested fields
codeenumrequired

Stable, machine-readable failure code for this job type.

Acceptsinput_validationcontent_moderationinvalid_fileaudio_too_longtransload_failedgeneration_failedtimeoutinsufficient_credits
detailsarray of objectoptionalJobErrorDetail ↗

Optional structured failure details.

Show 3 nested fields
fieldstringoptional

Dotted path to the offending input, when applicable.

messagestringrequired

Human-readable explanation of this detail.

typestringrequired

The category of this detail entry.

messagestringrequired

Human-readable failure message.

job_iduuidrequired

Stable identifier of the job and of the resource it produces.

resultobjectoptionalFabricVideo ↗

The produced resource. Present once the job is COMPLETED.

Show 1 nested field
videoobjectrequiredFile ↗

Generated lip-synced video.

Show 4 nested fields
content_typestringoptional

The mime type of the file.

file_namestringoptional

The name of the file.

file_sizeintegeroptional

The size of the file in bytes.

urlstringrequired

The URL where the file can be downloaded from.

statusenumrequired

Current lifecycle state of the job.

AcceptsPROCESSINGCOMPLETEDFAILEDCANCELLED

status moves through PROCESSING, COMPLETED, FAILED, CANCELLED. Only COMPLETED carries a result; only FAILED carries an error.

When a job fails

There are two kinds of failure, and the SDK raises a different exception for each:

  • A request the API rejects, before any job exists, raises a subclass of veed.APIError, with status_code, code and request_id.
  • A job that was accepted but failed while rendering raises veed.JobFailedError from generate() and wait(). job holds the final job, and code is one of input_validation, content_moderation, invalid_file, audio_too_long, transload_failed, generation_failed, timeout, insufficient_credits.
example.py
import veed

try:
  job = client.fabric.generate(...)
except veed.JobFailedError as e:
  print(e.code, e.message)
except veed.APIError as e:
  print(e.status_code, e.code, e.request_id)

A cancelled job raises veed.JobCancelledError. If timeout runs out first, veed.WaitTimeoutError is raised, but the job keeps rendering: call wait() with its id to pick it up again.

get() raises none of these for a failed job. It returns the job as it is, so check status yourself, and treat anything other than COMPLETED and PROCESSING as terminal: a CANCELLED or FAILED job will not change state again, and polling it further only burns rate limit.

Async

veed.AsyncVeed has the same methods, as coroutines:

example.py
import veed

async with veed.AsyncVeed() as client:
  job = await client.fabric.generate(...)

What it costs

It costs $0.08–$0.15 per second of the generated video, billed on the output rather than on the source image. The pricing page breaks that down per resolution.

Next

  • Try the same inputs in the playground before you write any code
  • Every other model follows this submit-and-poll shape — the reference pages list the inputs each one takes