API Reference
AI Video Generation API
Generate and edit video from text prompts and optional image, video, or audio references.
API generations are separate from dashboard history. Anawaiting_uploaddraft is usable for 15 minutes, untiluploadExpiresAt. A started job remains accessible untilresultExpiresAt, normally one hour after it completes, fails, or finishes cancellation. Access stops at that deadline, and a central cleanup sweep deletes the record and media shortly afterward.
For current credit costs, see API credit costs.
OpenAPI-style reference
API endpoints
Generate or edit video from text prompts and optional image, video, or audio references.
/api/v1 are auto-cleaned and do not count toward the user's storage quota./api/v1/ai-video-generation/modelsList AI video models
Returns the public model IDs, supported inputs, aspect ratios, exact model-specific output tiers, and duration policies. Resolution aliases are not accepted.
Parameters
No parameters.
Examples
Response
{
"models": [
{
"id": "kling-v3",
"name": "Kling 3",
"acceptedAssets": [
"image"
],
"supportsPublicUrlReferences": true,
"publicUrlRequirements": {
"protocol": "https",
"publiclyDownloadable": true,
"redirectsValidated": true
},
"aspectRatios": [
"16:9",
"9:16",
"1:1"
],
"resolutions": [
"720p",
"1080p"
],
"resolutionPolicy": {
"semantics": "resolution_tier",
"values": [
"720p",
"1080p"
],
"exactValueRequired": true,
"aliasesAccepted": false
},
"durationMode": "fixed",
"durationPolicy": {
"mode": "fixed",
"valuesSeconds": [
5,
10,
15
],
"acceptsThreeSecondRequest": false
},
"durations": [
5,
10,
15
],
"supportsGeneratedAudio": true,
"creditsPerSecond": 6
}
]
}Responses
| Status | Description |
|---|---|
200 | Model catalog returned |
401 | Missing, invalid, revoked, or unauthorized API key |
429 | Rate limit exceeded; wait for the Retry-After interval before retrying |
500 | Unexpected request-processing error |
/api/v1/ai-video-generation/uploadsCreate reference upload URLs
Creates an awaiting_upload draft and signed upload URLs for supported multimodal references. PUT every file to its returned URL before starting the job. The draft and its upload URLs are usable until uploadExpiresAt, normally 15 minutes after creation. After that deadline, attempts to replay or start the retained draft return 410 until the periodic central cleanup sweep removes it shortly afterward; generationId lookups then return 404. Skip this endpoint for text-only generation.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
Idempotency-Key | string | Yes | Required stable key for safely retrying upload-draft creation; reuse the same key when starting this draft header |
modelId | string | Yes | Public model ID returned by GET /models body |
prompt | string | Yes | Text instructions for the video; generation settings are saved with the upload draft body |
assets | array | Yes | One or more supported reference assets for the selected model body |
assets[].id | string | Yes | Client-defined stable asset ID used to reference the upload body |
assets[].kind | image | video | audio | Yes | Reference media type body |
assets[].fileName | string | Yes | Original file name body |
assets[].contentType | string | Yes | Allowed MIME type for the selected asset kind body |
assets[].fileSize | number | Yes | Exact file size in bytes body |
assets[].tag | string | No | Optional prompt tag such as @reference_1; a stable tag is generated when omitted body |
assets[].role | reference | start_frame | end_frame | audio_reference | source_video | style | No | Optional semantic role. Use a role supported by the selected model body |
assets[].durationSeconds | number | No | Required for source video and for model inputs with duration limits body |
assets[].width | number | No | Optional declared media width in pixels body |
assets[].height | number | No | Optional declared media height in pixels body |
Examples
Request
{
"modelId": "kling-v3",
"prompt": "Use the reference as the opening frame, then move the camera forward slowly",
"aspectRatio": "16:9",
"resolution": "720p",
"durationSeconds": 5,
"generateAudio": false,
"assets": [
{
"id": "reference-1",
"kind": "image",
"fileName": "reference.jpg",
"contentType": "image/jpeg",
"fileSize": 2483921
}
]
}Response
{
"generationId": "aiapi_...",
"status": "awaiting_upload",
"estimatedCredits": 30,
"uploadExpiresIn": 900,
"uploadExpiresAt": "2026-07-21T11:45:00.000Z",
"uploads": [
{
"id": "reference-1",
"kind": "image",
"method": "PUT",
"contentType": "image/jpeg",
"declaredFileSize": 2483921,
"uploadUrl": "https://signed-upload-url..."
}
],
"startUrl": "/api/v1/ai-video-generation/jobs"
}Responses
| Status | Description |
|---|---|
200 | Existing upload draft returned for an idempotent replay |
201 | Upload draft and signed upload URLs created |
400 | Invalid request body, asset declaration, or unsupported model setting |
401 | Missing, invalid, revoked, or unauthorized API key |
402 | Insufficient credits; no draft is created |
409 | Idempotency-Key was already used with a different upload request, or the matching draft has already started |
410 | The matching upload draft passed uploadExpiresAt but has not yet been removed by the central cleanup sweep; after removal the same generationId returns 404 |
413 | A declared reference asset exceeds the media-type size limit |
429 | Rate limit exceeded; wait for the Retry-After interval before retrying |
500 | Unexpected request-processing or upload-URL creation error |
/api/v1/ai-video-generation/jobsStart an AI video job
Starts a text-only or multimodal job. Send direct public HTTPS references in assets, or send the generationId returned by POST /uploads after every signed upload finishes successfully. The upload-to-start flow is the one idempotency exception: POST /jobs must reuse the upload draft's Idempotency-Key even though its body is only { generationId }; all other reuse with a different body returns 409. No artificial wait is required after the final successful PUT. Subclip validates each signed upload and saves an immutable job input before generation starts. A retryable upload_missing (400), upload_verification_failed (502), or upload_lock_failed (502) response includes Retry-After; retry the identical generationId request with exponential backoff and jitter, capped at 15 seconds, until uploadExpiresAt. Create a new draft only after 410 upload_expired. Public HTTPS references are fetched and validated asynchronously after a job is accepted, so an unreachable, private, changed, oversized, or invalid media URL can later produce status failed with error.code INPUT_INVALID. Credits are verified before the job is queued. durationSeconds is the native generation duration, not a trim target. The API does not trim outputs; use the selected model's live durationPolicy.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
Idempotency-Key | string | Yes | Required. Reusing it with the same body returns the original job; a different body returns 409. Exception: when starting a POST /uploads draft, reuse that draft's key with the { generationId } body header |
generationId | string | No | For a multimodal job, send this as the only body field after every PUT succeeds; retry the same value and Idempotency-Key when a retryable finalization error is returned body |
modelId | string | No | Required unless generationId is provided body |
prompt | string | No | Required unless generationId is provided body |
assets | array | No | Optional direct public HTTPS reference assets. Do not combine this with generationId body |
assets[].url | string | No | Direct publicly downloadable HTTPS media URL. Fetching and media validation are asynchronous; a URL that is unreachable, private, changed, oversized, or invalid can later end the accepted job as failed with error.code INPUT_INVALID body |
assets[].kind | image | video | audio | No | Reference media type supported by the selected model body |
assets[].role | reference | start_frame | end_frame | audio_reference | source_video | style | No | Optional semantic role supported by the selected model body |
assets[].tag | string | No | Optional prompt tag such as @image_1; generated when omitted body |
assets[].fileName | string | No | Optional display file name; inferred from the URL path when omitted body |
assets[].contentType | string | No | Optional expected MIME type; actual downloaded media is independently verified body |
assets[].durationSeconds | number | No | Optional declared duration; required by models that require source-duration metadata body |
aspectRatio | string | No | One exact value from the selected model's aspectRatios returned by GET /models body |
resolution | string | No | Exact, case-sensitive model-specific output tier from GET /models resolutionPolicy.values. Aliases are not accepted body |
durationSeconds | number | No | Native generation duration. For fixed mode, one value from durationPolicy.valuesSeconds; for source_asset mode, the verified source duration within its range. This API does not trim output body |
generateAudio | boolean | No | Generate native audio when the selected model supports it bodydefault: false |
Examples
Request
{
"modelId": "kling-v3",
"prompt": "Use @image_1 as the opening frame, then move the camera forward slowly",
"aspectRatio": "16:9",
"resolution": "720p",
"durationSeconds": 5,
"generateAudio": false,
"assets": [
{
"kind": "image",
"role": "reference",
"url": "https://cdn.yoursite.com/reference.jpg"
}
]
}Response
{
"generationId": "aiapi_...",
"status": "queued",
"estimatedCredits": 30,
"statusUrl": "/api/v1/ai-video-generation/jobs/aiapi_...",
"downloadUrl": "/api/v1/ai-video-generation/jobs/aiapi_.../download",
"resultExpiresAt": null
}Responses
| Status | Description |
|---|---|
200 | Existing job returned for an idempotent replay |
202 | Generation accepted and queued. Public-URL media is fetched and validated asynchronously; poll status for a possible terminal INPUT_INVALID error |
400 | Invalid request or unsupported model setting; upload_missing for a signed-upload draft is retryable after Retry-After |
401 | Missing, invalid, revoked, or unauthorized API key |
402 | Insufficient credits; no generation job is queued |
404 | The supplied generationId was not found or is not accessible with this API key |
409 | Idempotency conflict or the requested draft cannot be started in its current state |
410 | The upload or retained-result deadline passed but the record has not yet been removed by the central cleanup sweep; after removal the generationId returns 404 |
413 | A declared or verified signed-upload asset exceeds the media-type size limit |
429 | Rate limit exceeded; wait for the Retry-After interval before retrying |
500 | Unexpected request-processing error |
502 | upload_verification_failed or upload_lock_failed while starting a signed-upload draft; wait for Retry-After, then retry the same generationId, body, and Idempotency-Key |
503 | Generation queueing is temporarily unavailable; retry the identical request with the same Idempotency-Key |
/api/v1/ai-video-generation/jobs/{generationId}Get AI video job status
Returns status, progress, credit usage, output metadata, and the access deadline. The public state vocabulary is: awaiting_upload for an unstarted upload draft; queued or processing for an active job; and completed, failed, or canceled for a terminal outcome. Poll the same generationId while it is queued or processing. cancellationPending can be true only after an explicit POST /cancel request; if it is true, keep polling until it becomes false. A public HTTPS reference that fails asynchronous download or media validation ends as failed with error.code INPUT_INVALID. Terminal jobs remain accessible until resultExpiresAt, normally one hour after the final outcome. After that deadline, this endpoint returns 410 until the periodic central cleanup sweep removes the record shortly afterward, then returns 404.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
generationId | string | Yes | Generation ID returned by POST /uploads or POST /jobs path |
Examples
Response
{
"generationId": "aiapi_...",
"status": "completed",
"progress": 100,
"modelId": "kling-v3",
"estimatedCredits": 30,
"creditsUsed": 30,
"outputReady": true,
"outputCount": 1,
"cancellationPending": false,
"error": null,
"resultExpiresAt": "2026-07-21T12:30:00.000Z"
}Responses
| Status | Description |
|---|---|
200 | Job status returned |
401 | Missing, invalid, revoked, or unauthorized API key |
404 | Generation was not found, has been removed by the central cleanup sweep, or is not accessible with this API key |
410 | resultExpiresAt passed, but the periodic central cleanup sweep has not removed the record yet |
429 | Rate limit exceeded; wait for the Retry-After interval before polling again |
500 | Unexpected request-processing error |
/api/v1/ai-video-generation/jobs/{generationId}/downloadCreate AI video download URLs
Returns signed URLs only after status is completed and outputReady is true. The downloads array can contain multiple files; iterate over every downloads[] entry instead of assuming one output. Each URL is valid only until the earlier of its expiresAt and the job's resultExpiresAt. resultExpiresAt is normally one hour after the final outcome. After that deadline, this endpoint returns 410 until the periodic central cleanup sweep removes the job and media shortly afterward, then returns 404.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
generationId | string | Yes | Completed generation ID path |
Examples
Response
{
"generationId": "aiapi_...",
"downloads": [
{
"id": "output-1",
"index": 0,
"downloadUrl": "https://signed-download-url...",
"fileName": "generation.mp4",
"contentType": "video/mp4",
"fileSize": 18345678
}
],
"expiresIn": 1800,
"expiresAt": "2026-07-21T12:00:00.000Z",
"resultExpiresAt": "2026-07-21T12:30:00.000Z"
}Responses
| Status | Description |
|---|---|
200 | Download URLs created for every output |
401 | Missing, invalid, revoked, or unauthorized API key |
404 | Generation was not found, has been removed by the central cleanup sweep, or is not accessible with this API key |
409 | The job is not completed with outputReady true |
410 | resultExpiresAt passed, but the periodic central cleanup sweep has not removed the job and media yet |
429 | Rate limit exceeded; wait for the Retry-After interval before retrying |
500 | Unexpected request-processing or signed-URL creation error |
/api/v1/ai-video-generation/jobs/{generationId}/cancelCancel an AI video job
Requests explicit user cancellation for an awaiting_upload draft or a queued or processing job. Use this endpoint only when the user chose to cancel, not because progress is slow. cancellationPending is exposed only for this explicit-cancel flow; while it is true, resultExpiresAt is null and the client must keep polling the same generationId. When cancellationPending becomes false, resultExpiresAt starts the normal one-hour access window. After that deadline, requests return 410 until the periodic central cleanup sweep removes the job and media shortly afterward, then return 404.
Parameters
| Field | Type | Required | Details |
|---|---|---|---|
generationId | string | Yes | awaiting_upload, queued, or processing generation ID path |
Examples
Response
{
"generationId": "aiapi_...",
"status": "canceled",
"cancellationPending": true,
"resultExpiresAt": null
}Responses
| Status | Description |
|---|---|
200 | Cancellation state returned; cancellationPending can be true only for this explicit cancel request |
401 | Missing, invalid, revoked, or unauthorized API key |
404 | Generation was not found, has been removed by the central cleanup sweep, or is not accessible with this API key |
409 | The job can no longer be canceled in its current state |
410 | resultExpiresAt passed, but the periodic central cleanup sweep has not removed the record yet |
429 | Rate limit exceeded; wait for the Retry-After interval before retrying |
500 | Unexpected request-processing error |
Available models
Call /api/v1/ai-video-generation/models for the current machine-readable limits. Only public model IDs and capabilities are returned.
resolution is a model-specific output tier. Send an exact, case-sensitive value from resolutionPolicy.values (also returned in resolutions); aliases are not accepted. GPT Image 2 uses quality tiers such as Low, Medium, and High, while other models may use resolution tiers such as 1K, 2K, or 4K. These values are not interchangeable.
durationSeconds is the model's native duration, not a final trim target. Read durationPolicy from this endpoint: fixed-duration models require one listed value; Gemini Omni Flash is edit-only and must match a verified 3–10 second source video. No configured text-to-video or image-to-video model currently accepts a native 3-second request. For an exact 3.000-second deliverable, trim a supported generated duration after download. This API does not trim outputs, and its credit estimate uses the native generated duration.
| Model ID | Name | Inputs | Aspect ratios | Output tiers | Native duration |
|---|---|---|---|---|---|
gemini-omni-flash | Gemini Omni Flash | text, video | source | source | Source video, 3–10s (must match source) |
kling-v3 | Kling 3 | text, image | 16:9, 9:16, 1:1 | 720p, 1080p | 5s, 10s, 15s |
veo-3-1 | Veo 3.1 | text, image | 16:9, 9:16 | 720p, 1080p | 4s, 6s, 8s |
runway-gen-4-5 | Runway Gen-4.5 | text, image | 16:9, 9:16, 1:1 | 720p | 5s, 10s |
seedance-2 | Seedance 2 | text, image, video, audio | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 720p, 1080p | 5s, 10s, 15s |
pika-2-2 | Pika 2.2 | text, image | 16:9, 9:16, 1:1, 4:5, 5:4, 3:2, 2:3 | 720p, 1080p | 5s, 10s |
luma-ray-2 | Luma Ray 2 | text, image | 16:9, 9:16, 1:1 | 720p | 5s, 9s |
hailuo-2-3 | Hailuo 2.3 | text, image | 16:9, 9:16 | 768p, 1080p | 6s, 10s |
pixverse-v6 | PixVerse V6 | text, image | 16:9, 9:16, 1:1, 4:3, 3:4 | 720p, 1080p | 5s, 10s, 15s |
wan-2-7 | Wan 2.7 | text, audio | 16:9, 9:16, 1:1, 4:3, 3:4 | 720p, 1080p | 5s, 10s, 15s |
ltx-2-3-pro | LTX 2.3 Pro | text, image, video, audio | 16:9, 9:16 | 1080p, 1440p, 2160p | 6s, 8s, 10s |
Text-only generation
Send the prompt and model settings directly to POST /jobs. Idempotency-Key is required: repeating the same request returns the original job, while changing the body for that key returns 409. The only cross-body exception is the signed-upload transition described below: start that same draft with its original key and a body containing its generationId.
curl -X POST https://www.subclip.app/api/v1/ai-video-generation/jobs \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-request-001" \
-d '{
"modelId": "kling-v3",
"prompt": "A cinematic tracking shot of a sailboat crossing calm water at sunrise",
"aspectRatio": "16:9",
"resolution": "720p",
"durationSeconds": 5,
"generateAudio": false
}'Multimodal generation
If the reference is already hosted, include its direct public HTTPS download URL in POST /jobs. Subclip rejects private-network hosts. Downloading and media verification continue asynchronously after acceptance, so an unreachable URL, invalid file, or mismatched media type can later produce status: failed with error.code: INPUT_INVALID.
curl -X POST https://www.subclip.app/api/v1/ai-video-generation/jobs \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-request-url-001" \
-d '{
"modelId": "ltx-2-3-pro",
"prompt": "Continue @video_1 with a slow cinematic push toward the subject",
"aspectRatio": "16:9",
"resolution": "1080p",
"durationSeconds": 6,
"generateAudio": false,
"assets": [
{
"kind": "video",
"role": "source_video",
"url": "https://cdn.yoursite.com/reference.mp4"
}
]
}'For a local file, use the signed-upload flow:
- Create an
awaiting_uploaddraft with the prompt, model settings, and asset metadata throughPOST /uploads. Its response includes the 15-minuteuploadExpiresAtdeadline anduploadExpiresIn. - PUT every file to its returned URL using the declared content type and exact content length. Wait for every PUT response and require a successful status.
- Start the saved draft with
POST /jobs, a body containing only the returnedgenerationId, and the sameIdempotency-Keyused forPOST /uploads. This upload-to-start step is the intentional exception to the usual same-key/same-body rule.
No client-side delay is required after every PUT completes successfully. Subclip verifies each upload and copies it to immutable job input storage before dispatch. If this step returns upload_missing (400), upload_verification_failed (502), or upload_lock_failed (502), treat Retry-After as the minimum delay and retry POST /jobs with the same generationId, identical body, and same Idempotency-Key. Use exponential backoff with jitter (for example 1, 2, 4, and 8 seconds, capped at 15 seconds) and stop at uploadExpiresAt. Do not create a second draft unless the API returns 410 upload_expired.
curl -X POST https://www.subclip.app/api/v1/ai-video-generation/uploads \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-request-002" \
-d '{
"modelId": "kling-v3",
"prompt": "Use the reference as the opening frame, then move the camera forward slowly",
"aspectRatio": "16:9",
"resolution": "720p",
"durationSeconds": 5,
"generateAudio": false,
"assets": [
{
"id": "reference-1",
"kind": "image",
"fileName": "reference.jpg",
"contentType": "image/jpeg",
"fileSize": 2483921
}
]
}'curl -X PUT "$UPLOAD_URL" \ -H "Content-Type: image/jpeg" \ -H "Content-Length: 2483921" \ --upload-file ./reference.jpg
curl -X POST https://www.subclip.app/api/v1/ai-video-generation/jobs \
-H "Authorization: Bearer $SUBCLIP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-request-002" \
-d '{
"generationId": "aiapi_..."
}'Status, download, cancel, and automatic retention
POST /uploads creates an awaiting_upload draft, not a running generation. After POST /jobs returns 200 or 202, keep polling that same generationId every five seconds while it is queued or processing. Do not submit a replacement or change its model while it is active.
Treat status as authoritative. The public vocabulary is awaiting_upload for a draft; queued and processing for a started, non-terminal job; and completed, failed, or canceled for outcomes. Completed and failed are terminal; canceled is terminal when cancellationPending is false. That flag can be true only after an explicit cancel request. When it is true, keep polling that same canceled job until it becomes false.
Download only after status: completed and outputReady: true. Save every item in downloads before the earlier of its signed-URL expiresAt and the job's resultExpiresAt. Use cancel only for an explicit user cancellation. A processing job can temporarily return status: canceled, cancellationPending: true, and resultExpiresAt: null; its one-hour access window starts when cancellation finishes.
Starting or replaying a draft after uploadExpiresAt returns 410 upload_expired. Status, download, or cancel requests for a terminal result past resultExpiresAt return 410 generation_expired. After the central sweep removes either record, its generationId returns 404 generation_not_found.
curl https://www.subclip.app/api/v1/ai-video-generation/jobs/aiapi_... \ -H "Authorization: Bearer $SUBCLIP_API_KEY"
DOWNLOAD_JSON=$(curl -s https://www.subclip.app/api/v1/ai-video-generation/jobs/aiapi_.../download \ -H "Authorization: Bearer $SUBCLIP_API_KEY") echo "$DOWNLOAD_JSON" | jq -r '.downloads[] | [.downloadUrl, .fileName] | @tsv' | \ while IFS=$'\t' read -r DOWNLOAD_URL FILE_NAME; do curl -L "$DOWNLOAD_URL" -o "$FILE_NAME" done
curl -X POST https://www.subclip.app/api/v1/ai-video-generation/jobs/aiapi_.../cancel \ -H "Authorization: Bearer $SUBCLIP_API_KEY"
Credits and errors
402 insufficient_credits, pass_required, or billing_unavailable means the credit preflight failed. No generation is dispatched and no generation credits are charged.
400 upload_missing means an expected signed upload was not found after Subclip's verification attempts. Confirm every PUT succeeded, then follow Retry-After and retry the same draft.
502 upload_verification_failed means storage returned a non-missing error while Subclip was checking an uploaded object. The bytes may still exist; retry the same draft rather than uploading again.
502 upload_lock_failed means the uploaded bytes were found but could not be copied or streamed into immutable job storage. It is a retryable server-side storage finalization failure.
409 idempotency_conflict means the same idempotency key was reused with a different request body, or the requested lifecycle action is invalid for the current status.
503 enqueue_failed means dispatch was temporarily unavailable. Retry the identical request with the same Idempotency-Key.
When a response includes Retry-After—including rate limits and retryable upload finalization errors—wait at least that many seconds before retrying.
A public-URL asset that cannot be downloaded or validated asynchronously can make the accepted job end as failed with error.code: INPUT_INVALID.
410 upload_expired applies when starting or replaying a retained draft after uploadExpiresAt. 410 generation_expired applies to terminal-result access after resultExpiresAt. After the central sweep deletes the record, its generationId returns 404 generation_not_found.
Once a job is accepted, a polling response is never a reason to retry POST /jobs. Continue polling the same generationId. Only a terminal failed response carries an actionable generation error; use its retryable value before deciding whether to create a new request with a new Idempotency-Key.