API reference
Submit a prompt to start a video generation and reserve its Ink.
POST https://loopling.ai/v1/video/generations
string · required
Your API key, as Bearer lpl_….
Idempotency-Keystring
Makes a retry return the first generation instead of starting another. 1–128 letters, digits, ., _, : or -.
modelenum<string> · required
The model to run.
Available options: seedance-2.5, seedance-2.0, seedance-2.0-fast
promptstring
What to generate. Up to 4,000 characters. Optional with a first frame or an image or video reference.
aspect_ratioenum<string> · default: "16:9"
Frame shape. adaptive follows the first frame or the references.
Available options: 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, adaptive
resolutionenum<string> · default: "720p"
Output resolution. Some models and aspect ratios cap it (unsupported_resolution).
Available options: 480p, 720p, 1080p
durationinteger · default: 5
Length in whole seconds. No "auto".
4–30 (seedance-2.5) · 4–15 (seedance-2.0, seedance-2.0-fast)
generate_audioboolean · default: true
Adds a synchronised audio track. Included in the price.
seedinteger
-1 to 2147483647. Best-effort reproducibility, not guaranteed.
first_frame_urlstring<uri>
Opening frame: a public https image.
last_frame_urlstring<uri>
Closing frame. Needs first_frame_url.
reference_image_urlsstring<uri>[]
Style and subject references.
≤ 30 (seedance-2.5) · ≤ 9 (seedance-2.0, seedance-2.0-fast)
reference_video_urlsstring<uri>[]
Motion references.
≤ 10 (seedance-2.5) · ≤ 3 (seedance-2.0, seedance-2.0-fast)
reference_audio_urlsstring<uri>[]
Sound references. On seedance-2.0 and seedance-2.0-fast, send an image or video beside them.
≤ 10 (seedance-2.5) · ≤ 3 (seedance-2.0, seedance-2.0-fast)
metadataobject
Your own JSON, up to 4 KiB. Echoed back in request.
idstring
Generation ID, vgen_….
objectenum<string>
Object type.
Available options: video.generation
statusenum<string>
succeeded and failed are final.
Available options: queued, running, succeeded, failed
progressinteger
0–100 while queued or running, when reported.
modelstring
The model ID.
created_atstring<date-time>
When it was submitted.
completed_atstring<date-time>
When it reached succeeded or failed.
videoobject
The clip. On succeeded, until the result expires.
urlstring<uri>
Signed MP4 link. Needs no key; works until expires_at.
expires_atstring<date-time>
When url stops working. Read the generation again for a fresh link.
content_typestring
Always video/mp4.
duration_secondsnumber
Seconds billed: the reported length, never more than requested, or the requested length when none is reported.
resolutionstring
The requested resolution, or the default.
aspect_ratiostring
The requested aspect ratio, or the default. adaptive comes back as adaptive.
size_bytesinteger
File size in bytes.
result_expiredboolean
true once the clip is past retention. The record stays.
seedinteger
The seed used, when reported.
ink_reservedinteger
Ink held at submit: the most this generation can cost.
usd_payg_reservednumber
ink_reserved in pay-as-you-go USD.
ink_chargedinteger
Ink charged, once final. 0 when failed.
usd_payg_chargednumber
ink_charged in pay-as-you-go USD.
requestobject
The request as accepted: defaults filled in, metadata inside.
errorobject
Why it failed. On failed only.
codestring
The reason, such as provider_failed, timeout, result_persistence_failed, admission_failed or provider_rejected.
messagestring
A sentence for your logs.
curl
curl https://loopling.ai/v1/video/generations \
-H "Authorization: Bearer $LOOPLING_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.5",
"prompt": "A paper lantern drifting over a night harbour, slow dolly in",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 5
}'Node
const res = await fetch('https://loopling.ai/v1/video/generations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LOOPLING_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'seedance-2.5',
prompt: 'A paper lantern drifting over a night harbour, slow dolly in',
aspect_ratio: '16:9',
resolution: '1080p',
duration: 5,
}),
})
const generation = await res.json() // { id: 'vgen_…', status: 'queued', ink_reserved, … }Python
import os, requests
res = requests.post(
"https://loopling.ai/v1/video/generations",
headers={"Authorization": f"Bearer {os.environ['LOOPLING_API_KEY']}"},
json={
"model": "seedance-2.5",
"prompt": "A paper lantern drifting over a night harbour, slow dolly in",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 5,
},
)
generation = res.json() # {"id": "vgen_…", "status": "queued", "ink_reserved": …}{
"id": "vgen_01J9X6K3M8Q2",
"object": "video.generation",
"status": "queued",
"model": "seedance-2.5",
"created_at": "2026-10-04T08:12:44Z",
"ink_reserved": 320,
"usd_payg_reserved": 3.2,
"request": {
"model": "seedance-2.5",
"prompt": "A paper lantern drifting over a night harbour, slow dolly in",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 5,
"generate_audio": true
}
}{
"error": {
"type": "invalid_request_error",
"code": "invalid_duration",
"message": "duration must be an integer between 4 and 30 seconds for seedance-2.5",
"param": "duration",
"request_id": "0b7c6f1e-2d4a-4e8b-9c3f-5a1d7e2b8c40"
}
}{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "The API key is invalid, revoked or expired.",
"request_id": "0b7c6f1e-2d4a-4e8b-9c3f-5a1d7e2b8c40"
}
}{
"error": {
"type": "insufficient_ink_error",
"code": "insufficient_ink",
"message": "This request needs 320 Ink but only 106 Ink is available. Top up in Inkwell and retry.",
"request_id": "0b7c6f1e-2d4a-4e8b-9c3f-5a1d7e2b8c40"
}
}{
"error": {
"type": "rate_limit_error",
"code": "concurrency_limit",
"message": "At most 2 video generations may run at once on this account; wait for one to finish.",
"request_id": "0b7c6f1e-2d4a-4e8b-9c3f-5a1d7e2b8c40"
}
}Answers 202 at once with a queued generation. Poll it for the result.
Start from frames or from references, not both (conflicting_references).
first_frame_url opens the shot. last_frame_url closes it and needs a first frame.Caps per generation, by model:
| Model | Duration | Resolutions | Aspect ratios | References |
|---|---|---|---|---|
seedance-2.5 | 4–30 s | 480p, 720p, 1080p | 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, adaptive · 9:16, adaptive: up to 720p | 50: 30 images · 10 videos · 10 audio |
seedance-2.0 | 4–15 s | 480p, 720p, 1080p | 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, adaptive | 12: 9 images · 3 videos · 3 audio |
seedance-2.0-fast | 4–15 s | 480p, 720p | 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, adaptive | 12: 9 images · 3 videos · 3 audio |
Over a per-kind cap answers too_many; over the total, reference_limit. seedance-2.0 and seedance-2.0-fast need an image or video beside any audio; seedance-2.5 takes audio alone.
Every frame and reference is a URL. Each must be:
https://, with no credentials in it;Each URL is fetched once at submit. No copy is kept.
Send an Idempotency-Key. A retry with the same key returns the first generation: no second hold, no second render.
curl
curl https://loopling.ai/v1/video/generations \
-H "Authorization: Bearer $LOOPLING_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-8812-lantern" \
-d '{
"model": "seedance-2.5",
"prompt": "A paper lantern drifting over a night harbour, slow dolly in",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 5
}'Node
const res = await fetch('https://loopling.ai/v1/video/generations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LOOPLING_API_KEY}`,
'Content-Type': 'application/json',
// Same key on every retry of this job: one hold, one render.
'Idempotency-Key': 'job-8812-lantern',
},
body: JSON.stringify({
model: 'seedance-2.5',
prompt: 'A paper lantern drifting over a night harbour, slow dolly in',
aspect_ratio: '16:9',
resolution: '1080p',
duration: 5,
}),
})
const replayed = res.headers.get('Idempotent-Replayed') === 'true'Python
import os, requests
res = requests.post(
"https://loopling.ai/v1/video/generations",
headers={
"Authorization": f"Bearer {os.environ['LOOPLING_API_KEY']}",
# Same key on every retry of this job: one hold, one render.
"Idempotency-Key": "job-8812-lantern",
},
json={
"model": "seedance-2.5",
"prompt": "A paper lantern drifting over a night harbour, slow dolly in",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 5,
},
)
replayed = res.headers.get("Idempotent-Replayed") == "true"Scope and edge cases: Idempotency.
Each 400 below names the field in param, except invalid_idempotency_key, which has none.
code | When |
|---|---|
unknown_model | model isn't a listed ID. |
invalid_enum | resolution or aspect_ratio isn't a known value. |
unsupported_resolution | The model doesn't offer that resolution, or not at that aspect ratio. |
invalid_duration | duration isn't a whole number in the model's range. |
unsupported_parameter | duration is "auto" or -1. |
invalid_type | A field has the wrong type. |
too_long | prompt is over 4,000 characters, a URL over 2,048, or metadata over 4 KiB. |
invalid_url | A URL isn't https://, or carries credentials. |
too_many | A reference list is over its per-kind cap. |
reference_limit | Over the model's total, or audio with no image or video. |
conflicting_references | Frames and references in one request. |
missing_first_frame | last_frame_url without first_frame_url. |
missing_prompt | No prompt, no first frame, and no image or video reference. |
invalid_seed | seed is outside -1 to 2147483647. |
invalid_idempotency_key | The Idempotency-Key header is malformed. |
Auth, Ink and limit errors are in Errors.
loopling.ai · grows with you, grows itself
hello@loopling.ai · for agents · community · pricing · Enterprise · API · privacy · terms