# Create your first video

Run one complete call on your own server: submit, wait, download, then check the charge. The example sends a five-second Seedance 2.0 Fast request.

## Before you start

You need a verified account with Credits, and a project that can use the model. Set two environment variables on your server:

| Variable | Purpose |
| --- | --- |
| `VIZO_API_KEY` | Project key with `models:read`, `generations:create`, `generations:read`, `generations:list` (to find a task after a lost response) and `assets:download`. |
| `VIZO_BILLING_KEY` | Read-only billing key with `billing:read`, used only to check the charge at the end. |

## 1. Get an API key

`GET /v1/models`

Create a project key in the console under API keys. The full key is shown once, so store it in your server secrets right away. Never put it in a web page, an app or a repository.

Call `listModels` to confirm the key works. Each entry is one route your project can order, with its `modelId`, `operation`, `inputSchema` and reference price.

### cURL

```bash
# Keep the key on the server only
export VIZO_API_KEY="va_xxxxxxxx"

curl https://api.vizoapi.com/v1/models \
  -H "Authorization: Bearer $VIZO_API_KEY"
```

### Python

```python
import os, requests

api = "https://api.vizoapi.com/v1"
headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}

models = requests.get(f"{api}/models", headers=headers, timeout=30).json()
for m in models["entries"]:
    print(m["modelId"], m["operation"])
```

### Node.js

```javascript
const api = "https://api.vizoapi.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
  "Content-Type": "application/json",
};

const models = await fetch(`${api}/models`, { headers }).then((r) => r.json());
console.log(models.entries.map((m) => `${m.modelId} ${m.operation}`));
```

### 200

```json
{
  "entries": [
    {
      "modelId": "seedance-2-0-fast",
      "operation": "text-to-video",
      "routeId": "seedance-2-0-fast-720p",
      "inputSchema": {
        "…": "…"
      }
    }
  ],
  "next_cursor": ""
}
```

## 2. Submit the generation

`POST /v1/generations`

Send `model`, `operation` and `input` straight to `POST /v1/generations`. HTTP 202 means the task is accepted; save the returned `id` at once.

A new task may be `pending_funding` for a moment: no Credits are reserved until funding is confirmed, then it becomes `queued`. If funding is refused, the task closes with `error.code` set to `insufficient_balance`, `budget_exceeded` or `spending_blocked`.

Set `metadata.client_reference` to your own order number. It lets you find the task again if the response is lost.

> **Send the create request once.** Every accepted POST is its own task with its own hold and charge, even when the content is identical. Turn off automatic retries for this request in your HTTP client and proxy.

> **After a timeout or disconnect, look before you resubmit.** With a task ID, read the task with `getGeneration`. Without one, list recent tasks with `listGenerations` and find it by `metadata.client_reference`. The list is newest first. Narrow it with `model`, `source`, `status`, `created_after` (inclusive) and `created_before` (exclusive).

### cURL

```bash
# Send once: do not let a client or proxy retry this request
curl https://api.vizoapi.com/v1/generations \
  -H "Authorization: Bearer $VIZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2-0-fast",
    "operation": "text-to-video",
    "input": { "prompt": "A paper boat drifting on a pond", "duration_seconds": 5 },
    "metadata": { "client_reference": "order-1042" }
  }'
```

### Python

```python
import os, requests

api = "https://api.vizoapi.com/v1"
headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}

# Send once: do not retry this request automatically
task = requests.post(f"{api}/generations", headers=headers, json={
    "model": "seedance-2-0-fast",
    "operation": "text-to-video",
    "input": {"prompt": "A paper boat drifting on a pond", "duration_seconds": 5},
    "metadata": {"client_reference": "order-1042"},
}, timeout=60).json()

task_id = task["id"]  # save it now
```

### Node.js

```javascript
const api = "https://api.vizoapi.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
  "Content-Type": "application/json",
};

// Send once: do not retry this request automatically
const task = await fetch(`${api}/generations`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    model: "seedance-2-0-fast",
    operation: "text-to-video",
    input: { prompt: "A paper boat drifting on a pond", duration_seconds: 5 },
    metadata: { client_reference: "order-1042" },
  }),
}).then((r) => r.json());

const taskId = task.id; // save it now
```

### 202

```json
{
  "id": "gen_01JD8Z5QW3XR",
  "status": "pending_funding",
  "execution_status": "pending_funding",
  "delivery_status": "pending",
  "billing_status": "pending",
  "version": 1,
  "price": {
    "unit": "credit",
    "reserved": "0.000",
    "quoted": "1400.000",
    "charged": "0.000"
  },
  "allowed_actions": [
    "cancel"
  ],
  "customer_resolution_deadline": "2026-11-03T08:00:12Z",
  "created_at": "2026-11-02T08:00:12Z",
  "model": "seedance-2-0-fast",
  "source": "api",
  "closed_at": null,
  "late_result_available": false,
  "error": null,
  "result": null,
  "metadata": {
    "client_reference": "order-1042"
  }
}
```

## 3. Wait for the result

`GET /v1/generations/{id}`

Poll the same task until `closed_at` is not null. A task has three separate states; read each one:

- `execution_status`: progress of the model run
- `delivery_status`: whether the result is stored; `ready` means you can download it
- `billing_status`: held, charged or released

When `closed_at` is set and `status` is not `succeeded`, there is no result to download. Read `error.code` to see why, for example `insufficient_balance`, `budget_exceeded` or `spending_blocked`. A task that never received funding has `charged` of `0.000`.

> Prefer webhooks to polling? Create an endpoint in the console and pass `webhook_endpoint_id` when you submit. After verifying the signature, read the task by ID and trust that read.

### cURL

```bash
curl https://api.vizoapi.com/v1/generations/$GENERATION_ID \
  -H "Authorization: Bearer $VIZO_API_KEY"
```

### Python

```python
import os, requests

api = "https://api.vizoapi.com/v1"
headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}
import time

while True:
    task = requests.get(f"{api}/generations/{task_id}", headers=headers, timeout=30).json()
    if task["closed_at"]:
        break
    time.sleep(5)

if task["status"] != "succeeded":
    # Closed without a result, for example insufficient_balance, budget_exceeded or spending_blocked
    raise SystemExit(f"task closed: {task['status']} {(task['error'] or {}).get('code')}")
print(task["status"], task["price"]["charged"])
```

### Node.js

```javascript
const api = "https://api.vizoapi.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
  "Content-Type": "application/json",
};

let task;
do {
  await new Promise((r) => setTimeout(r, 5000));
  task = await fetch(`${api}/generations/${taskId}`, { headers }).then((r) => r.json());
} while (!task.closed_at);

if (task.status !== "succeeded") {
  // Closed without a result, for example insufficient_balance, budget_exceeded or spending_blocked
  throw new Error(`task closed: ${task.status} ${task.error?.code}`);
}
console.log(task.status, task.price.charged);
```

### 200

```json
{
  "id": "gen_01JD8Z5QW3XR",
  "status": "succeeded",
  "execution_status": "succeeded",
  "delivery_status": "ready",
  "billing_status": "settled",
  "version": 4,
  "price": {
    "unit": "credit",
    "reserved": "1400.000",
    "quoted": "1400.000",
    "charged": "1400.000"
  },
  "allowed_actions": [],
  "customer_resolution_deadline": "2026-11-03T08:00:12Z",
  "created_at": "2026-11-02T08:00:12Z",
  "model": "seedance-2-0-fast",
  "source": "api",
  "closed_at": "2026-11-02T08:01:43Z",
  "late_result_available": false,
  "error": null,
  "result": {
    "asset_id": "ast_01JD8Z9M2C4V",
    "outputs": [
      {
        "index": 0,
        "asset_id": "ast_01JD8Z9M2C4V",
        "content_type": "video/mp4"
      }
    ]
  },
  "metadata": {
    "client_reference": "order-1042"
  }
}
```

## 4. Download the result

`GET /v1/assets/{id}/download`

Use `result.asset_id` to request a short-lived download URL, then fetch that URL on its own. Do not send the API key with it. When the URL expires, request a new one for the same asset.

When a task has several outputs, download them in the order of `result.outputs`. Finished files are kept for 7 days; remove one earlier with `deleteAsset`.

### cURL

```bash
# 1. Ask for a short-lived download URL
curl https://api.vizoapi.com/v1/assets/$ASSET_ID/download \
  -H "Authorization: Bearer $VIZO_API_KEY"

# 2. Fetch the returned url on its own, without the API key
curl -o result.mp4 "$DOWNLOAD_URL"
```

### Python

```python
import os, requests

api = "https://api.vizoapi.com/v1"
headers = {"Authorization": f"Bearer {os.environ['VIZO_API_KEY']}"}

if task["status"] != "succeeded" or not task["result"]:
    raise SystemExit("no result to download: read task['error']['code']")
asset_id = task["result"]["asset_id"]
dl = requests.get(f"{api}/assets/{asset_id}/download", headers=headers, timeout=30).json()

# Fetch the signed URL on its own, without the API key
with requests.get(dl["url"], stream=True, timeout=300) as r, open("result.mp4", "wb") as f:
    for chunk in r.iter_content(1 << 20):
        f.write(chunk)
```

### Node.js

```javascript
import { writeFile } from "node:fs/promises";

if (task.status !== "succeeded" || !task.result) throw new Error("no result to download: read task.error.code");
const dl = await fetch(`${api}/assets/${task.result.asset_id}/download`, { headers }).then((r) => r.json());

// Fetch the signed URL on its own, without the API key
const media = await fetch(dl.url);
await writeFile("result.mp4", Buffer.from(await media.arrayBuffer()));
```

### 200

```json
{
  "url": "https://media.vizoapi.com/d/ast_01JD8Z9M2C4V?sig=…",
  "url_expires_at": "2026-11-02T08:16:43Z"
}
```

## 5. Check the charge

`GET /v1/ledger`

Read `/v1/ledger` with a read-only billing key and follow `next_cursor` through every page. Subtract the reversals from the settlement entries of this task; the result equals the task's `price.charged`.

A failed or timed-out task shows `charged` as `0.000`, and its hold has returned to your available balance.

### cURL

```bash
curl "https://api.vizoapi.com/v1/ledger?limit=100" \
  -H "Authorization: Bearer $VIZO_BILLING_KEY"

# When next_cursor is not empty, read the next page
curl "https://api.vizoapi.com/v1/ledger?limit=100&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $VIZO_BILLING_KEY"
```

### Python

```python
import os, requests

api = "https://api.vizoapi.com/v1"
billing = {"Authorization": f"Bearer {os.environ['VIZO_BILLING_KEY']}"}

entries, cursor = [], ""
while True:
    page = requests.get(f"{api}/ledger", headers=billing, timeout=30,
                        params={"limit": 100, "cursor": cursor}).json()
    entries += page["items"] or []
    cursor = page.get("next_cursor")
    if not cursor:
        break
```

### Node.js

```javascript
const api = "https://api.vizoapi.com/v1";
const billing = { Authorization: `Bearer ${process.env.VIZO_BILLING_KEY}` };

const entries = [];
let cursor = "";
do {
  const page = await fetch(`${api}/ledger?limit=100&cursor=${cursor}`, { headers: billing }).then((r) => r.json());
  entries.push(...(page.items ?? []));
  cursor = page.next_cursor;
} while (cursor);
```

### 200

```json
{
  "items": [
    {
      "id": "le_01JD8ZA1T6",
      "reference": "gen_01JD8Z5QW3XR",
      "from": "wallet:held",
      "to": "platform:settled",
      "credits": "1400.000",
      "created_at": "2026-11-02T08:01:43Z"
    }
  ],
  "next_cursor": ""
}
```

Amounts and IDs in sample responses are examples. Your requests return their own.

## Next

- [Image, audio and other video models](/models): Each model page lists its own fields and prices
- [Full reference for creating a generation](/docs/api/createGeneration): Request fields, responses and error codes
- [Receive webhook notifications](/docs/api/generationEvent): Verify, deduplicate and handle events out of order
- [Check an interrupted submission](/docs/retries): Timeouts, lost responses and duplicate tasks
