Video jobs and results
Poll video status, download output, handle generation failures and verify webhook signatures.
Last updated on
Save the id returned by video creation. Use an API key for the same workspace.
Check status
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.jsonpolling_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
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.binindex 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.