cicora.ai / docs
Video API

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

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

StatusAction
pendingWaiting to be processed
in_progressGeneration is running
completedOutput is ready to download
failedRead error_code and error
cancelled, expiredTerminal 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

Terminal
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:

CodeWhat to check
VIDEO_INPUT_UNAVAILABLESource URLs and their expiry
VIDEO_UPSTREAM_INVALID_REQUESTRequest settings and model constraints
VIDEO_CONTENT_REJECTED, VIDEO_COPYRIGHT_RESTRICTEDRequest and source-media policies
VIDEO_UPSTREAM_RATE_LIMITED, VIDEO_UPSTREAM_UNAVAILABLE, VIDEO_UPSTREAM_TIMEOUTWhether to retry later
VIDEO_UPSTREAM_INVALID_RESPONSE, VIDEO_ARTIFACT_STORAGE_FAILED, VIDEO_GENERATION_FAILEDKeep 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

On this page