Get started
Make your first clip: create a key, submit, poll, download.
curl, Node 18 or later, or Python 3 with requests.Create a key on API keys and copy it now: it's shown once. Export it where your code runs:
export LOOPLING_API_KEY="lpl_…"
Every sample reads the key from LOOPLING_API_KEY.
Send the prompt. The call answers 202 with the generation's id.
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
}
}A 402 means your balance can't cover the hold. Top up and resend.
Read the generation every 5 seconds until status is succeeded or failed. Renders usually take one to a few minutes.
curl
curl https://loopling.ai/v1/video/generations/vgen_01J9X6K3M8Q2 \
-H "Authorization: Bearer $LOOPLING_API_KEY"Node
const headers = { Authorization: `Bearer ${process.env.LOOPLING_API_KEY}` }
async function waitForVideo(id) {
for (;;) {
const res = await fetch(`https://loopling.ai/v1/video/generations/${id}`, { headers })
const gen = await res.json()
if (gen.status === 'succeeded') return gen.video // { url, expires_at, duration_seconds, … }
if (gen.status === 'failed') throw new Error(gen.error?.message ?? 'generation failed')
await new Promise((r) => setTimeout(r, 5000))
}
}
const video = await waitForVideo('vgen_01J9X6K3M8Q2')
// video.url is signed and valid for 15 minutes; read the generation again for a fresh one.Python
import os, time, requests
headers = {"Authorization": f"Bearer {os.environ['LOOPLING_API_KEY']}"}
def wait_for_video(generation_id: str) -> dict:
while True:
gen = requests.get(f"https://loopling.ai/v1/video/generations/{generation_id}", headers=headers).json()
if gen["status"] == "succeeded":
return gen["video"] # {"url": …, "expires_at": …, "duration_seconds": …}
if gen["status"] == "failed":
raise RuntimeError(gen.get("error", {}).get("message", "generation failed"))
time.sleep(5)
video = wait_for_video("vgen_01J9X6K3M8Q2")
# video["url"] is signed and valid for 15 minutes; read the generation again for a fresh one.A finished generation carries video.url:
{
"id": "vgen_01J9X6K3M8Q2",
"object": "video.generation",
"status": "succeeded",
"model": "seedance-2.5",
"created_at": "2026-10-04T08:12:44Z",
"completed_at": "2026-10-04T08:15:02Z",
"video": {
"url": "https://media.loopling.ai/…?sig=…",
"expires_at": "2026-10-04T08:30:02Z",
"content_type": "video/mp4",
"duration_seconds": 5,
"resolution": "1080p",
"aspect_ratio": "16:9",
"size_bytes": 8421376
},
"ink_reserved": 320,
"usd_payg_reserved": 3.2,
"ink_charged": 320,
"usd_payg_charged": 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
}
}Save the MP4 while its link is fresh. Each call to /content signs a new one.
curl
# 302 to a fresh signed URL; -L follows it and -o saves the MP4.
curl -L https://loopling.ai/v1/video/generations/vgen_01J9X6K3M8Q2/content \
-H "Authorization: Bearer $LOOPLING_API_KEY" \
-o lantern.mp4Node
import fs from 'node:fs'
const clip = await fetch('https://loopling.ai/v1/video/generations/vgen_01J9X6K3M8Q2/content', {
headers: { Authorization: `Bearer ${process.env.LOOPLING_API_KEY}` },
redirect: 'follow',
})
await fs.promises.writeFile('lantern.mp4', Buffer.from(await clip.arrayBuffer()))Python
import os, requests
res = requests.get(
"https://loopling.ai/v1/video/generations/vgen_01J9X6K3M8Q2/content",
headers={"Authorization": f"Bearer {os.environ['LOOPLING_API_KEY']}"},
allow_redirects=True,
)
open("lantern.mp4", "wb").write(res.content)The three steps as one script, each call reading the last one's answer:
curl
# 1. Submit: 202 with the generation id.
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
}'
# 2. Poll every few seconds until "status" is "succeeded" or "failed".
curl https://loopling.ai/v1/video/generations/vgen_01J9X6K3M8Q2 \
-H "Authorization: Bearer $LOOPLING_API_KEY"
# 3. Download.
# 302 to a fresh signed URL; -L follows it and -o saves the MP4.
curl -L https://loopling.ai/v1/video/generations/vgen_01J9X6K3M8Q2/content \
-H "Authorization: Bearer $LOOPLING_API_KEY" \
-o lantern.mp4Node
import fs from 'node:fs'
// 1. Submit
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, … }
// 2. Poll
const headers = { Authorization: `Bearer ${process.env.LOOPLING_API_KEY}` }
async function waitForVideo(id) {
for (;;) {
const res = await fetch(`https://loopling.ai/v1/video/generations/${id}`, { headers })
const gen = await res.json()
if (gen.status === 'succeeded') return gen.video // { url, expires_at, duration_seconds, … }
if (gen.status === 'failed') throw new Error(gen.error?.message ?? 'generation failed')
await new Promise((r) => setTimeout(r, 5000))
}
}
const video = await waitForVideo(generation.id)
// video.url is signed and valid for 15 minutes; read the generation again for a fresh one.
// 3. Download the MP4 while the link is fresh.
const file = await fetch(video.url)
await fs.promises.writeFile('lantern.mp4', Buffer.from(await file.arrayBuffer()))Python
# 1. Submit
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": …}
# 2. Poll
import time
headers = {"Authorization": f"Bearer {os.environ['LOOPLING_API_KEY']}"}
def wait_for_video(generation_id: str) -> dict:
while True:
gen = requests.get(f"https://loopling.ai/v1/video/generations/{generation_id}", headers=headers).json()
if gen["status"] == "succeeded":
return gen["video"] # {"url": …, "expires_at": …, "duration_seconds": …}
if gen["status"] == "failed":
raise RuntimeError(gen.get("error", {}).get("message", "generation failed"))
time.sleep(5)
video = wait_for_video(generation["id"])
# video["url"] is signed and valid for 15 minutes; read the generation again for a fresh one.
# 3. Download the MP4 while the link is fresh.
open("lantern.mp4", "wb").write(requests.get(video["url"]).content)loopling.ai · grows with you, grows itself
hello@loopling.ai · for agents · community · pricing · Enterprise · API · privacy · terms