# Get results

A task runs in the background. Poll it or receive a webhook, then download each output with a short-lived link.

## Task lifecycle

A create returns HTTP 202 with the task's `id`. Its `status` is `processing` from then until it closes:

| `status` | Meaning | Open |
| --- | --- | --- |
| `processing` | Accepted and not finished: the Credits hold is placed, the model works on it and the output is stored for download. | Yes |
| `succeeded` | Done. `result` lists the outputs. | No |
| `failed` | Closed without a result. `error.code` says why. | No |
| `canceled` | The system closed it before it started, for example because it was not funded or no model capacity freed up in time. Nothing is charged. | No |
| `unresolved` | Still unfinished at its deadline, `execution_expires_after` (24 hours by default; Seedance allows 1–72 hours). The hold is released and nothing is charged; treat it as failed. If the result arrives later, `late_result_available` becomes `true` and `result` is filled at no charge, with the webhook event `generation.late_result_available`. | No |

`execution_status` follows the same rule: `processing` until the model run ends. `billing_status` and `delivery_status` show funding and storage separately: `billing_status` is `pending` until Credits are held. Older releases listed the steps inside `processing` (`pending_funding`, `queued`, `submitting`, `submission_unknown`, `running`, `pending_investigation`, `finalizing`) as statuses; the list filter still accepts those names and treats each as `processing`.

A task is closed once `closed_at` is set. Only a closed task has its final `status`, `result` and `price.charged`.

## Poll

Read the task with [get a generation](/docs/api/getGeneration) every 5 to 10 seconds until `closed_at` is set.

```python title="Python"
import os, time, requests

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

def wait(task_id):
    while True:
        task = requests.get(f"{api}/generations/{task_id}", headers=headers, timeout=30).json()
        if task["closed_at"]:
            return task
        time.sleep(5)
```
```javascript title="Node.js"
const api = "https://api.vizoapi.com/v1";
const headers = { Authorization: `Bearer ${process.env.VIZO_API_KEY}` };

export async function wait(taskId) {
  for (;;) {
    const task = await fetch(`${api}/generations/${taskId}`, { headers }).then((r) => r.json());
    if (task.closed_at) return task;
    await new Promise((r) => setTimeout(r, 5000));
  }
}
```

## Or receive a webhook

Pass `webhook_endpoint_id` on the create to get a signed POST when the task closes. Treat the webhook as a signal and read the task again for the full result. See [Webhooks](/docs/webhooks).

## Download the outputs

`result.outputs` lists each output with its `asset_id`, in order. A video model may also return `result.last_frame_asset_id` when you asked for the last frame. Request a download link for each asset with [download an asset](/docs/api/downloadAsset), then fetch the file before `url_expires_at`.

```bash title="cURL"
URL=$(curl -fsS https://api.vizoapi.com/v1/assets/$ASSET_ID/download \
  -H "Authorization: Bearer $VIZO_API_KEY" | jq -r .url)
curl -fsS -o output.mp4 "$URL"
```

Outputs are kept for 7 days after they are created. Store the files you want to keep on your side.

## Canceling

A task cannot be canceled through the API. Once accepted it runs to its end, and it is charged only if it succeeds. Stopping your polling does not stop it. Only the system cancels a task, before it starts; the task then closes as `canceled`, its hold is released and a `generation.canceled` event is sent. `allowed_actions` is always empty.

## Late results

If the model delivers after the task has already closed without a result, the task gets `late_result_available: true` and a `result`, and a `generation.late_result_available` event is sent. Downloading a late result is free; the task's charge does not change.
