Video generation
Create a video from a prompt, from an image, or from a first and last frame. Returns a job immediately — rendering takes tens of seconds to minutes.
Implemented
Request
POST
/videos/generationsRequires the Authorization: Bearer apl_... header
| Field | Type | Description |
|---|---|---|
modelrequired | string | Public model ID whose kind is VIDEO. |
promptrequired | string | Describe the scene, motion and style. Max 4000 characters. |
duration_secondsrequired | 4 | 6 | 8 | Only 4, 6 or 8. Anything else is rejected before the provider is called. |
aspect_ratio | "16:9" | "9:16" | Defaults to 16:9. Use 9:16 for vertical video. |
image | ImageInput | First frame, for image-to-video. Same shape as image on the edit endpoint. |
last_frame | ImageInput | Last frame. Must be sent together with image. |
Response
| Field | Type | Description |
|---|---|---|
idrequired | string | The ApiLux job ID (vidjob_...). Use it to poll and download. |
statusrequired | "queued" | "running" | "completed" | "failed" | Status at creation time, usually queued. |
requested_duration_secondsrequired | integer | The number of seconds you were charged for. |
apilux.credit_chargedrequired | integer | Credits charged at creation. |
Billing
Charged at job creation, by the duration_seconds you requested. Polling costs nothing extra. A failed job is refunded automatically, exactly once.
Examples
curl https://apilux.net/api/v1/videos/generations \
-H "Authorization: Bearer apl_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "VIDEO_MODEL_ID",
"prompt": "Sáng sớm trên biển, chuyển động điện ảnh",
"duration_seconds": 4,
"aspect_ratio": "16:9"
}'import os, time, requests
BASE = "https://apilux.net/api/v1"
HEAD = {"Authorization": f"Bearer {os.environ['APILUX_API_KEY']}"}
job = requests.post(
f"{BASE}/videos/generations",
headers=HEAD,
json={
"model": "VIDEO_MODEL_ID",
"prompt": "Sáng sớm trên biển, chuyển động điện ảnh",
"duration_seconds": 4,
},
).json()
while True:
time.sleep(5)
state = requests.get(f"{BASE}/videos/generations/{job['id']}", headers=HEAD).json()
if state["status"] in ("completed", "failed"):
break
if state["status"] == "completed":
mp4 = requests.get(BASE.rsplit("/v1", 1)[0] + state["content_url"], headers=HEAD)
open("video.mp4", "wb").write(mp4.content)Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Invalid body (missing field, wrong type, value out of range). |
| 401 | auth_missing_or_malformed | Missing Authorization header, or it is not in the Bearer apl_... form. |
| 401 | auth_invalid | The API key does not exist. |
| 401 | auth_revoked | The API key has been revoked. |
| 401 | auth_expired | The API key has expired. |
| 400 | unknown_model | The model does not exist on ApiLux. |
| 400 | capability_not_supported | The model exists but cannot be used on this endpoint (e.g. a text model on an image endpoint). |
| 503 | pricing_not_configured | The model is implemented but has no price configured on ApiLux yet. This is our state, not a client error. |
| 503 | model_disabled | The model is currently disabled. |
| 503 | model_temporarily_unavailable | The model is enabled and priced, but the upstream provider is not currently serving it. This is temporary and can clear on its own — the model reappears in GET /v1/models once the provider serves it again. You are not charged and no provider call is made. |
| 402 | insufficient_balance | Wallet balance is not enough for this request. |
| 409 | duplicate_request | A create request with the same Idempotency-Key is already in progress or has completed. ApiLux does NOT call the provider again and does NOT charge twice. If the original request finished, the response carries job_id so you can fetch the result there. Applies to the media create endpoints (images/generations, images/edits, videos/generations). |
| 429 | api_key_limit_exceeded | This API key hit a limit you configured yourself. The wallet still has funds — only this key is capped until the window resets. The response carries limit_type (spending | requests | tokens) and period (daily | monthly | lifetime). Token limits on text endpoints are measured from actual usage, so the total can exceed the limit by at most one request. |
| 503 | api_key_quota_reconciliation_required | An earlier text request on this same key has not been reconciled yet. The usual cause: the provider returned a successful result without token usage, so the token limit for that window can no longer be trusted and ApiLux stops rather than let the limit be exceeded silently. Retrying will not help until that is done — contact support. You are not charged for the blocked request, no provider call is made, and image/video endpoints are unaffected. |
| 429 | rate_limit_exceeded | Rate limit exceeded (requests per second or per minute). Carries a Retry-After header and a retry_after_seconds field; limit_type says which axis blocked. A blocked request never reaches the provider and is never charged. Wait the indicated seconds, then retry. |
| 503 | system_maintenance | ApiLux is under maintenance. All generation endpoints are paused; you are not charged and no provider call is made. Retry later. |
| 503 | ai_requests_disabled | An operator has paused AI requests while the rest of the service keeps running. Distinct from system_maintenance because it is a narrower switch. |
| 503 | provider_catalog_stale | ApiLux no longer has recent enough evidence of what the provider is serving, so it stops rather than charge for a request that may fail. This is our problem, not a removed model — it clears as soon as one successful sync returns. Retrying in a few minutes is reasonable. |
| 502 | upstream_error | The model provider failed. You are not charged. |
| 504 | upstream_timeout | The provider did not respond in time. You are not charged. |
| 429 | upstream_rate_limited | Rate limited. Retry after a few seconds. |
Notes
If the create request already returned an
id, do not resend it — poll that id. Resending creates a second job and charges again.