Skip to content

AI Video

Text-to-video generation with status polling and cancellation.

1. Overview

Base path: https://api.infrai.cc/v1/video
Auth header: Authorization: Bearer $INFRAI_API_KEY
bash
# Call any /v1/video capability over raw HTTP — no SDK to install.
# curl:
curl https://api.infrai.cc/v1/video/... \
  -H "Authorization: Bearer $INFRAI_API_KEY" \
  -H "Content-Type: application/json"

2. Methods (7)

2.1video.generate

POST /v1/video/generate

Start a text-to-video generation job.

Parameters

NameTypeRequiredDescription
promptstring
Required
Text description of the desired video.
reference_imagestringOptionalOptional reference image URL.
duration_secondsnumberOptionalClip duration in seconds.
1–60default: 5
aspect_ratio"16:9" | "9:16" | "1:1"OptionalAspect ratio.
default: "16:9"
resolution"720p" | "1080p" | "4k"OptionalOutput resolution (720p/1080p/4k); gated per vendor.
default: "720p"
modelstringOptionalExplicit model id for video generation (e.g. "kling-v2", "cogvideox").
idempotency_keystringOptionalOptional dedup key; identical retries return the same result.

Returns

VideoJob { job_id, state, video_url?, thumbnail_url?, error? }
NameTypeDescription
job_idstringUnique identifier for this async job
pattern: ^vid_[A-Za-z0-9]{20,}$
state"queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"Current lifecycle state of this resource
modelstringModel identifier to use for generation
vendorstringVendor that handled or will handle this request
promptstringText prompt for content generation
duration_secondsintegerDuration of generated video in seconds
≥ 1
aspect_ratio"16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9"Aspect ratio for generated images or video (e.g. 16:9)
video_urlstring | nullURL to the generated video file
thumbnail_urlstring | nullURL to the video thumbnail image
cost_usdnumber | nullCost of this operation in USD
≥ 0
created_atstringISO 8601 timestamp when this resource was created
format: date-time
finished_atstring | nullISO 8601 timestamp when the job finished
format: date-time
retention_daysinteger | nullNumber of days to retain this resource
≥ 0
parent_job_idstring | nullSource job this one was derived from via video.extend / video.upscale / webhook re-run; null for original generate jobs.
pattern: ^vid_[A-Za-z0-9]{20,}$
errorobjectVendor/gateway error detail; present only when state is failed.
error.codestringStable identifier from registry.yaml.
error.http_statusintegerHTTP status code of the webhook delivery attempt
400–599
error.messagestringHuman-readable hint.
error.docs_urlstringDocumentation URL for this error code
format: uri
error.code_detailstringSub-classification (e.g. wallet cap reason).
error.retryablebooleanWhether the error is retryable
error.retry_after_msintegerSuggested retry delay in milliseconds
≥ 0
error.paramstringWhich parameter failed validation, if applicable.
error.trace_idstringDistributed tracing identifier
error.request_idstringServer-assigned request identifier for tracing
metadataobjectCost/vendor/model disclosure for this generation (cost-transparency contract surface); present once a vendor has been selected.
metadata.request_idstringUUID v7.
metadata.trace_idstringDistributed tracing identifier
metadata.timestampstringUnix timestamp of the data point
format: date-time
metadata.latency_msintegerLatency of the operation in milliseconds
≥ 0
metadata.entry_formanyEntry point format (rest, mcp, sdk)
metadata.cost_usdnumberCost of this operation in USD
≥ 0
metadata.cost_cnynumberCost in CNY for this request
≥ 0
metadata.vendorstringAdapter name; references VendorRegistry.
metadata.vendor_region"china" | "western"Vendor region where the request was processed
metadata.markup_pct0 | 0.05Markup percentage applied on top of vendor cost
metadata.cache_layer"none" | "l1_hit" | "l1_miss" | "l2_hit" | "batch"Cache layer that served the response
metadata.failover_infoobjectFailover details when a different vendor was used
metadata.failover_info.primary_vendorstringPrimary vendor that was attempted
metadata.failover_info.actual_vendorstringVendor that actually handled the request (after failover)
metadata.failover_info.primary_modelstringPrimary model that was attempted
metadata.failover_info.actual_modelstringModel that actually handled the request (after failover)
metadata.failover_info.cost_at_primarynumberCost at the primary vendor in USD
≥ 0
metadata.failover_info.cost_at_actualnumberCost at the actual vendor in USD
≥ 0
metadata.failover_info.cost_multipliernumberCost multiplier due to failover
≥ 0
metadata.failover_info.failover_reason"VENDOR_TIMEOUT" | "VENDOR_DOWN" | "RATE_LIMIT_VENDOR" | "VENDOR_AUTH_ERROR"Reason for the failover
metadata.failover_info.fallback_chain_depthintegerDepth of the failover chain traversed
≥ 1
metadata.edge_popstringGeographic PoP that served this request.
metadata.worker_idstringWorker process identifier that handled the request
metadata.idempotent_replaybooleantrue when this write was served from an earlier idempotent result (Idempotency-Key replay). Gateway-set; SDKs MUST read it from metadata, not a header.

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X POST https://api.infrai.cc/v1/video/generate \
  -H "Authorization: Bearer $INFRAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "hello"}'

2.2video.status

GET /v1/video/status/{id}

Poll the status of a video job.

Parameters

NameTypeRequiredDescription
idstring
Required
The job identifier.

Returns

VideoJob
NameTypeDescription
job_idstringUnique identifier for this async job
pattern: ^vid_[A-Za-z0-9]{20,}$
state"queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"Current lifecycle state of this resource
progress_pctinteger | nullProgress of the async operation as a percentage (0-100)
0–100
eta_secondsinteger | nullEstimated seconds until completion
≥ 0
current_stepstring | nullDescription of the current processing step

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X GET https://api.infrai.cc/v1/video/status/ID \
  -H "Authorization: Bearer $INFRAI_API_KEY"

2.3video.cancel

POST /v1/video/cancel/{id}

Cancel a running video job.

Parameters

NameTypeRequiredDescription
idstring
Required
The job identifier.

Returns

{ ok: boolean }
NameTypeDescription
job_idstringUnique identifier for this async job
pattern: ^vid_[A-Za-z0-9]{20,}$
state"queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"Current lifecycle state of this resource
modelstringModel identifier to use for generation
vendorstringVendor that handled or will handle this request
promptstringText prompt for content generation
duration_secondsintegerDuration of generated video in seconds
≥ 1
aspect_ratio"16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9"Aspect ratio for generated images or video (e.g. 16:9)
video_urlstring | nullURL to the generated video file
thumbnail_urlstring | nullURL to the video thumbnail image
cost_usdnumber | nullCost of this operation in USD
≥ 0
created_atstringISO 8601 timestamp when this resource was created
format: date-time
finished_atstring | nullISO 8601 timestamp when the job finished
format: date-time
retention_daysinteger | nullNumber of days to retain this resource
≥ 0
parent_job_idstring | nullSource job this one was derived from via video.extend / video.upscale / webhook re-run; null for original generate jobs.
pattern: ^vid_[A-Za-z0-9]{20,}$
errorobjectVendor/gateway error detail; present only when state is failed.
error.codestringStable identifier from registry.yaml.
error.http_statusintegerHTTP status code of the webhook delivery attempt
400–599
error.messagestringHuman-readable hint.
error.docs_urlstringDocumentation URL for this error code
format: uri
error.code_detailstringSub-classification (e.g. wallet cap reason).
error.retryablebooleanWhether the error is retryable
error.retry_after_msintegerSuggested retry delay in milliseconds
≥ 0
error.paramstringWhich parameter failed validation, if applicable.
error.trace_idstringDistributed tracing identifier
error.request_idstringServer-assigned request identifier for tracing
metadataobjectCost/vendor/model disclosure for this generation (cost-transparency contract surface); present once a vendor has been selected.
metadata.request_idstringUUID v7.
metadata.trace_idstringDistributed tracing identifier
metadata.timestampstringUnix timestamp of the data point
format: date-time
metadata.latency_msintegerLatency of the operation in milliseconds
≥ 0
metadata.entry_formanyEntry point format (rest, mcp, sdk)
metadata.cost_usdnumberCost of this operation in USD
≥ 0
metadata.cost_cnynumberCost in CNY for this request
≥ 0
metadata.vendorstringAdapter name; references VendorRegistry.
metadata.vendor_region"china" | "western"Vendor region where the request was processed
metadata.markup_pct0 | 0.05Markup percentage applied on top of vendor cost
metadata.cache_layer"none" | "l1_hit" | "l1_miss" | "l2_hit" | "batch"Cache layer that served the response
metadata.failover_infoobjectFailover details when a different vendor was used
metadata.failover_info.primary_vendorstringPrimary vendor that was attempted
metadata.failover_info.actual_vendorstringVendor that actually handled the request (after failover)
metadata.failover_info.primary_modelstringPrimary model that was attempted
metadata.failover_info.actual_modelstringModel that actually handled the request (after failover)
metadata.failover_info.cost_at_primarynumberCost at the primary vendor in USD
≥ 0
metadata.failover_info.cost_at_actualnumberCost at the actual vendor in USD
≥ 0
metadata.failover_info.cost_multipliernumberCost multiplier due to failover
≥ 0
metadata.failover_info.failover_reason"VENDOR_TIMEOUT" | "VENDOR_DOWN" | "RATE_LIMIT_VENDOR" | "VENDOR_AUTH_ERROR"Reason for the failover
metadata.failover_info.fallback_chain_depthintegerDepth of the failover chain traversed
≥ 1
metadata.edge_popstringGeographic PoP that served this request.
metadata.worker_idstringWorker process identifier that handled the request
metadata.idempotent_replaybooleantrue when this write was served from an earlier idempotent result (Idempotency-Key replay). Gateway-set; SDKs MUST read it from metadata, not a header.

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X POST https://api.infrai.cc/v1/video/cancel/ID \
  -H "Authorization: Bearer $INFRAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"job_id": "sample"}'

2.4video.get

GET /v1/video/get/{id}

获取视频任务的完整详情(状态、视频地址、时长、分辨率、厂商、模型等)。

Parameters

NameTypeRequiredDescription
idstring
Required
The job identifier.

Returns

VideoJob { job_id, state, video_url?, thumbnail_url?, duration_seconds, resolution, vendor, model, error? }
NameTypeDescription
job_idstringUnique identifier for this async job
pattern: ^vid_[A-Za-z0-9]{20,}$
state"queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"Current lifecycle state of this resource
modelstringModel identifier to use for generation
vendorstringVendor that handled or will handle this request
promptstringText prompt for content generation
duration_secondsintegerDuration of generated video in seconds
≥ 1
aspect_ratio"16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9"Aspect ratio for generated images or video (e.g. 16:9)
video_urlstring | nullURL to the generated video file
thumbnail_urlstring | nullURL to the video thumbnail image
cost_usdnumber | nullCost of this operation in USD
≥ 0
created_atstringISO 8601 timestamp when this resource was created
format: date-time
finished_atstring | nullISO 8601 timestamp when the job finished
format: date-time
retention_daysinteger | nullNumber of days to retain this resource
≥ 0
parent_job_idstring | nullSource job this one was derived from via video.extend / video.upscale / webhook re-run; null for original generate jobs.
pattern: ^vid_[A-Za-z0-9]{20,}$
errorobjectVendor/gateway error detail; present only when state is failed.
error.codestringStable identifier from registry.yaml.
error.http_statusintegerHTTP status code of the webhook delivery attempt
400–599
error.messagestringHuman-readable hint.
error.docs_urlstringDocumentation URL for this error code
format: uri
error.code_detailstringSub-classification (e.g. wallet cap reason).
error.retryablebooleanWhether the error is retryable
error.retry_after_msintegerSuggested retry delay in milliseconds
≥ 0
error.paramstringWhich parameter failed validation, if applicable.
error.trace_idstringDistributed tracing identifier
error.request_idstringServer-assigned request identifier for tracing
metadataobjectCost/vendor/model disclosure for this generation (cost-transparency contract surface); present once a vendor has been selected.
metadata.request_idstringUUID v7.
metadata.trace_idstringDistributed tracing identifier
metadata.timestampstringUnix timestamp of the data point
format: date-time
metadata.latency_msintegerLatency of the operation in milliseconds
≥ 0
metadata.entry_formanyEntry point format (rest, mcp, sdk)
metadata.cost_usdnumberCost of this operation in USD
≥ 0
metadata.cost_cnynumberCost in CNY for this request
≥ 0
metadata.vendorstringAdapter name; references VendorRegistry.
metadata.vendor_region"china" | "western"Vendor region where the request was processed
metadata.markup_pct0 | 0.05Markup percentage applied on top of vendor cost
metadata.cache_layer"none" | "l1_hit" | "l1_miss" | "l2_hit" | "batch"Cache layer that served the response
metadata.failover_infoobjectFailover details when a different vendor was used
metadata.failover_info.primary_vendorstringPrimary vendor that was attempted
metadata.failover_info.actual_vendorstringVendor that actually handled the request (after failover)
metadata.failover_info.primary_modelstringPrimary model that was attempted
metadata.failover_info.actual_modelstringModel that actually handled the request (after failover)
metadata.failover_info.cost_at_primarynumberCost at the primary vendor in USD
≥ 0
metadata.failover_info.cost_at_actualnumberCost at the actual vendor in USD
≥ 0
metadata.failover_info.cost_multipliernumberCost multiplier due to failover
≥ 0
metadata.failover_info.failover_reason"VENDOR_TIMEOUT" | "VENDOR_DOWN" | "RATE_LIMIT_VENDOR" | "VENDOR_AUTH_ERROR"Reason for the failover
metadata.failover_info.fallback_chain_depthintegerDepth of the failover chain traversed
≥ 1
metadata.edge_popstringGeographic PoP that served this request.
metadata.worker_idstringWorker process identifier that handled the request
metadata.idempotent_replaybooleantrue when this write was served from an earlier idempotent result (Idempotency-Key replay). Gateway-set; SDKs MUST read it from metadata, not a header.

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X GET https://api.infrai.cc/v1/video/get/ID \
  -H "Authorization: Bearer $INFRAI_API_KEY"

2.5video.delete

DELETE /v1/video/delete/{id}

删除指定视频任务及其产物(幂等写)。

Parameters

NameTypeRequiredDescription
idstring
Required
The job identifier.
idempotency_keystringOptionalOptional dedup key; identical retries return the same result.

Returns

{ ok: boolean }
NameTypeDescription
job_idstring | nullThe job affected; null for bulk ops (delete_all).
pattern: ^vid_[A-Za-z0-9]{20,}$

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X DELETE https://api.infrai.cc/v1/video/delete/ID \
  -H "Authorization: Bearer $INFRAI_API_KEY"

2.6video.download_url

GET /v1/video/download_url/{id}

为指定视频任务签发带过期时间的临时下载链接。

Parameters

NameTypeRequiredDescription
idstring
Required
The job identifier.

Returns

{ url: string, expires_at: string }
NameTypeDescription
job_idstringUnique identifier for this async job
pattern: ^vid_[A-Za-z0-9]{20,}$
urlstringSigned, time-limited download URL hosted by Infrai.
format: uri
expires_atstringISO 8601 timestamp when this resource or token expires
format: date-time
size_bytesinteger | nullSize of the resource in bytes
≥ 0
content_typestring | nullMIME type of the object

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X GET https://api.infrai.cc/v1/video/download_url/ID \
  -H "Authorization: Bearer $INFRAI_API_KEY"

2.7video.capabilities

GET /v1/video/capabilities

查询各视频厂商支持的模型、分辨率、最大时长及扩展/超分能力。

Returns

VideoCapabilities { vendors: Array<{ vendor, models, resolutions, max_duration_seconds, supports_extend, supports_upscale }> }
NameTypeDescription
modelsobject[]Served video models, one per priced (vendor, model) row.
models[].vendorstring-
models[].modelstring-
models[].regionstring | null-
models[].max_secondsinteger | nullMax generation duration the vendor adapter enforces (DURATION_EXCEEDED above this); null if the adapter is absent.
≥ 1
models[].image_to_videoboolean | nullWhether the vendor supports image-to-video; null if the adapter is absent.
resolutions("720p" | "1080p" | "4k")[]Supported resolution keys (also the keys of resolution_multiplier).
resolution_multiplierobjectPer-resolution cost-axis multiplier (e.g. 720p=1.0, 1080p=2.0, 4k=4.0).

Example

一次性前置(每个范例都假定已完成):

bash
# No SDK to install — every call is a plain HTTPS request.
# Get a project key by signing in at https://infrai.cc/login (Google/GitHub gives
# you $2 free credit; email sign-in starts at $0). On 402 INSUFFICIENT_CREDIT, add
# funds at https://infrai.cc/billing (or POST /v1/account/topup and open the
# returned checkout_url).
export INFRAI_API_KEY="ifr_..."
bash
curl -X GET https://api.infrai.cc/v1/video/capabilities \
  -H "Authorization: Bearer $INFRAI_API_KEY"
Advanced: pin a vendor

By default infrai routes each call to the best available provider — you do not pick a vendor. As an escape hatch, this capability accepts an optional vendor parameter to pin one specific provider. Every live vendor for this capability is available in real time from the discovery endpoint for the capability id — see the discovery API.

GET /v1/discovery/{capability}

video.generate

3. All capabilities

Every routed capability in this module — the complete public REST contract. The methods above are the guided walkthrough; this index is the full reference.

video.cancelPOST /v1/video/cancel/{id}

Cancel an in-progress video generation job.

Parameters (3)
NameTypeRequiredDescription
idstringRequiredPath parameter.
job_idstringRequiredId of the video job to cancel.
idempotency_keystring | nullOptionalClient-provided idempotency key; prevents duplicate execution on retry
video.capabilitiesGET /v1/video/capabilities

Query each video provider's supported models, resolutions, max duration, and extend/upscale capabilities.

No request parameters.

video.deleteDELETE /v1/video/delete/{id}

Delete a video job and its artifacts (idempotent write).

Parameters (1)
NameTypeRequiredDescription
idstringRequiredPath parameter.
video.download_urlGET /v1/video/download_url/{id}

Issue a time-limited, expiring download URL for a video job's output.

Parameters (1)
NameTypeRequiredDescription
idstringRequiredPath parameter.
video.generatePOST /v1/video/generate

Generate a video from a text or image prompt asynchronously (idempotent).

Parameters (12)
NameTypeRequiredDescription
promptstringRequiredText prompt for content generation
modelstring | nullOptionalModel selector. A specific video model id (e.g. 'wan-t2v'), OR a routing MODE: 'auto' (default, balanced value), 'cheapest' (lowest USD/s), 'smartest' (flagship). Null = auto.
resolution"720p" | "1080p" | "4k"OptionalOutput resolution spec axis. Gated per-vendor. (SSOT: enums/VideoResolution)
default: "720p"
duration_secondsintegerOptionalDuration of generated video in seconds
1–60default: 5
aspect_ratio"16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9"OptionalAspect ratio for generated images or video (e.g. 16:9)
default: "16:9"
seedinteger | nullOptionalRandom seed for deterministic generation
negative_promptstring | nullOptionalNegative prompt for excluding unwanted content
reference_imagestring | nullOptionalURL or base64 for image-to-video.
vendorstring | nullOptionalVendor that handled or will handle this request
webhook_urlstring | nullOptionalOptional callback URL; declares an async delivery target at submit time (events from VideoEventType). Equivalent to a video.webhook.subscribe for this job.
format: uri
idempotency_keystring | nullOptionalClient-provided idempotency key; prevents duplicate execution on retry
storebooleanOptionalOpt in to RETAIN the produced asset/record in Infrai's own store (default false = stateless passthrough: process, return inline, keep nothing). When true, Infrai persists the asset, bills a self-hosted storage line item and applies a retention TTL — and only then do the *.list/get/delete read caps see it.
default: false
video.getGET /v1/video/get/{id}

Retrieve a video job's full details (status, video URL, duration, resolution, provider, model).

Parameters (1)
NameTypeRequiredDescription
idstringRequiredPath parameter.
video.statusGET /v1/video/status/{id}

Query a video generation job's status and output URL.

Parameters (1)
NameTypeRequiredDescription
idstringRequiredPath parameter.

4. End-to-end example

A production-style walkthrough of this module: configure once, then run the flow. It exercises most of the module's APIs.

A copy-paste-runnable single-file Python program (stdlib only, no SDK): set your INFRAI_API_KEY, run it, and walk this module's core flow with REAL billed calls — later steps reuse real fields returned by earlier ones. The 12-line helper is the entire integration.

python
#!/usr/bin/env python3
"""Infrai · ai-video — runnable real-app example (single file, zero deps).

Copy this file, set your key, run it: every step is a REAL call to
api.infrai.cc, billed at the real (tiny) per-call price, printing the
live JSON response. Get a key at https://infrai.cc/login (Google/
GitHub sign-in grants $2 free credit); add funds at
https://infrai.cc/billing. No SDK — the 12-line helper below is the
entire integration."""
import json
import os
from urllib import error, request

KEY = os.environ.get("INFRAI_API_KEY") or "ifr_..."  # <- your key
BASE = "https://api.infrai.cc"


# Same raw HTTPS POST/GET as every per-method example on this page —
# wrapped once for reuse. There is nothing else to it: no SDK.
def infrai(method, path, body=None):
    req = request.Request(
        BASE + path, method=method,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"Authorization": f"Bearer {KEY}",
                 "Content-Type": "application/json"})
    try:
        with request.urlopen(req, timeout=60) as r:
            return json.loads(r.read())
    except error.HTTPError as e:
        return json.loads(e.read())


def show(label, resp):
    print(f"\n== {label} ==")
    print(json.dumps(resp, indent=2, ensure_ascii=False))
    return resp


# 1) video.generate — POST /v1/video/generate · Generate a video from a text or image prompt asynchronously (idempotent).  # NOTE: non-trivial real cost
r1 = show("video.generate", infrai("POST", "/v1/video/generate", {"prompt":"a timelapse of a city skyline at dusk","resolution":"1080p","duration_seconds":5}))

# 2) video.status — GET /v1/video/status/{id} · Query a video generation job's status and output URL.
id_2 = (r1.get("data") or {}).get("job_id") or ""
r2 = show("video.status", infrai("GET", f"/v1/video/status/{id_2}"))