# Video jobs and results

Source: https://cicora.ai/en/docs/video-jobs

Save the `id` returned by [video creation](/en/docs/video-generation). Use an API key for the same workspace.

## Check status

```bash
export VIDEO_ID="$(jq -er '.id' video-job.json)"

curl --fail-with-body --silent --show-error \
  "$API_BASE_URL/v1/videos/$VIDEO_ID" \
  -H "Authorization: Bearer $CICORA_API_KEY" -o video-status.json

jq '{id, status, error_code, error, unsigned_urls, usage}' video-status.json
```

`polling_url` may be relative: resolve it against the API base URL. While status is `pending` or `in_progress`, repeat GET with a delay such as five seconds. Bound your application's wait and retain the ID for later checks. A client timeout does not cancel the server job.

| Status                 | Action                               |
| ---------------------- | ------------------------------------ |
| `pending`              | Waiting to be processed              |
| `in_progress`          | Generation is running                |
| `completed`            | Output is ready to download          |
| `failed`               | Read `error_code` and `error`        |
| `cancelled`, `expired` | Terminal schema values; stop polling |

There is currently no public job cancellation endpoint. `generation_id` is an additional result identifier; use `id` for polling.

## Download output

```bash
test "$(jq -r '.status' video-status.json)" = "completed" || exit 1

curl --fail-with-body --silent --show-error \
  "$API_BASE_URL/v1/videos/$VIDEO_ID/content?index=0" \
  -H "Authorization: Bearer $CICORA_API_KEY" -o video-output.bin
```

`index` selects an output starting from zero. Determine the format from `Content-Type`; the example saves bytes without assuming a container. `unsigned_urls` contains content endpoints on our API. Despite the name, downloading requires `Authorization: Bearer ...`; these are not anonymous browser links.

## Webhooks

Set `callback_url` when creating the job. It must use HTTPS and resolve only to public IP addresses. Retrieve your workspace signing secret with `GET /v1/videos/webhook-secret` using your API key, and store it on your server.

The event body contains `type`, `created_at` and `data`. Terminal events use `video.generation.completed` and `video.generation.failed`; `data` contains `id`, `status` and the available result or error fields.

Verify `X-OpenRouter-Signature`, formatted as `t=<timestamp>,v1=<hex>`. Compute HMAC-SHA256 over `timestamp + "," + rawBody` using the retrieved secret. `rawBody` means the original UTF-8 bytes, not reserialized JSON. Compare signatures in constant time, validate timestamp freshness and deduplicate by `X-OpenRouter-Idempotency-Key` (`id-status`). This key deduplicates notifications, not generation creation.

Persist the event before returning `2xx` promptly: delivery waits up to 10 seconds. A webhook does not replace GET status when a notification is lost, and delivery failure does not change the generation outcome.

## Errors and retries

For a `failed` job, use `error_code` rather than parsing the message. Common codes:

| Code                                                                                          | What to check                          |
| --------------------------------------------------------------------------------------------- | -------------------------------------- |
| `VIDEO_INPUT_UNAVAILABLE`                                                                     | Source URLs and their expiry           |
| `VIDEO_UPSTREAM_INVALID_REQUEST`                                                              | Request settings and model constraints |
| `VIDEO_CONTENT_REJECTED`, `VIDEO_COPYRIGHT_RESTRICTED`                                        | Request and source-media policies      |
| `VIDEO_UPSTREAM_RATE_LIMITED`, `VIDEO_UPSTREAM_UNAVAILABLE`, `VIDEO_UPSTREAM_TIMEOUT`         | Whether to retry later                 |
| `VIDEO_UPSTREAM_INVALID_RESPONSE`, `VIDEO_ARTIFACT_STORAGE_FAILED`, `VIDEO_GENERATION_FAILED` | Keep the job ID and time for support   |

HTTP `200` on GET does not mean successful generation: inspect `status`. Final failure releases reserved funds. A new POST creates a separate billable job; confirm that the previous job has finished before retrying.

## Troubleshooting


**Job remains pending**


Keep the job ID and creation time for support. Continue polling with a delay; another POST creates a separate job.

## FAQ

### What is cicora.ai?

cicora.ai is a multi-model AI workspace and compatible API for chat, text, images, video, coding tools, and teams. Start with the [overview](/en.md), [documentation](/en/docs.md), or [model catalog](/en/models.md).

### How is usage billed?

Confirmed usage is charged in USD from the active workspace's prepaid balance. Use the live catalog for current model prices and the [usage guide](/en/docs/usage-costs.md) for settlement details.

### Which protocols and integrations are available?

Documentation covers OpenAI-compatible Chat Completions and Responses, Anthropic Messages, image interfaces, Claude Code, OpenCode, and Codex CLI. Compatibility does not imply support for every upstream parameter.

### Which sources are authoritative and current?

For model IDs, availability, capabilities, limits, and prices, use the [live catalog](/en/models.md). For API behavior, use the matching [documentation page](/en/docs.md). Never include API keys, private workspace data, or preview URLs in public documents.
