> Documentation index: https://api.veed.io/llms.txt. Fetch it to find every other page, as Markdown.

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

```bash
pip install veed
```

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

```bash
export VEED_API_KEY="vp_..."
```

## The whole loop

```python
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_url` · url · required — URL of the source image to animate.
- `audio_url` · url · required — URL of the audio track to lip-sync to.
- `resolution` · enum · required — Output video resolution. Accepts `720p`, `480p`.

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` — [reference](https://api.veed.io/docs/api/post-v1-fabric-1-0.md)

```python
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}` — [reference](https://api.veed.io/docs/api/get-v1-fabric-1-0-job-id.md)

```python
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:

```python
job = client.fabric.wait(job_id, timeout=1800)
```

The job envelope is the same either way:

- `credits_charged` · integer · optional — Credits charged for the job. Present once the job is COMPLETED and the final charge is known.
- `credits_estimated` · integer · optional — 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.
- `error` · object · optional — Failure information. Present once the job has FAILED.
- `job_id` · uuid · required — Stable identifier of the job and of the resource it produces.
- `result` · object · optional — The produced resource. Present once the job is COMPLETED.
- `status` · enum · required — Current lifecycle state of the job. Accepts `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`.

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

```python
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:

```python
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](https://api.veed.io/pricing) 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
