loopling.ai

Get started

Quickstart

Make your first clip: create a key, submit, poll, download.

Prerequisites

  • A Loopling account with a paid plan or one completed top-up. Top up on Billing.
  • curl, Node 18 or later, or Python 3 with requests.

1. Create a key

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.

2. Submit a generation

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.

3. Poll until it finishes

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
  }
}

4. Download the clip

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.mp4

Node

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.mp4

Node

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)

Next steps

  • Errors — Branch on one error shape and know which codes to retry.
  • Rate limits & concurrency — Stay within each key’s request rate and each account’s concurrency.
  • Idempotency — Retry a submit safely without paying for it twice.
  • Models & pricing — See each model’s limits and its Ink price per second of video.

Explore models

loopling.ai · grows with you, grows itself

hello@loopling.ai · for agents · community · pricing · Enterprise · API · privacy · terms