loopling.ai

Developer API

API guide · loopling

Guide · Video · Responses · Messages · Billing & Ink

Introduction

The Loopling API gives your code the models the studio runs — Seedance video, Astra and Claude — paid from the Ink wallet you already have. Three products, one key, one wallet:

ProductEndpointProtocolModels
VideoPOST /v1/video/generationsREST, async (submit → poll → download)seedance-2.5, seedance-2.0, seedance-2.0-fast
ResponsesPOST /v1/responsesOpenAI Responses protocolgpt-6-astra (alias astra)
MessagesPOST /v1/messagesAnthropic Messages protocolclaude-fable-5-1 (fable), claude-opus-5 (opus), claude-sonnet-5 (sonnet)

Every product is priced in Ink. Pay-as-you-go Ink is $1 = 100 Ink. See Billing & Ink for the full model.

Video is live wherever the API is open. The Astra and Claude lanes open after their vendor certification; until then their catalogue cards report available: false and calls answer 503 provider_unavailable. GET /v1/models is the honest source for what a deployment serves today.

This guide covers what every endpoint shares. The product pages cover each one in full: Video, Responses, Messages.

Authentication

Every request except the model catalogue carries a key in the Authorization header:

Authorization: Bearer lpl_…

A key is lpl_ followed by 43 URL-safe characters. Keys are created in Settings → API — the raw key is shown once, at creation, and never again; Loopling stores only a hash, a 12-character display prefix and the last four characters. Creating a key requires a paid plan or at least one completed Ink top-up.

  • A key belongs to one account and spends that account's Ink.
  • A key may carry an Ink cap: when its charged Ink reaches the cap, its calls answer 402 insufficient_ink_error with code: "key_ink_limit_reached" while the account's other keys keep working.
  • Revoking a key refuses new calls immediately with 401 invalid_api_key; generations already running still finish and settle.
  • Keep keys server-side. Never ship one in a browser or a mobile app.

A missing, malformed, revoked or unknown key answers 401 authentication_error with code: "invalid_api_key" and WWW-Authenticate: Bearer. Only the Authorization: Bearer header is read — the Anthropic SDK's x-api-key is not, so pass your key as its auth_token / authToken (see Messages).

curl

curl https://loopling.ai/v1/me \
  -H "Authorization: Bearer $LOOPLING_API_KEY"

Node

const res = await fetch('https://loopling.ai/v1/me', {
  headers: { Authorization: `Bearer ${process.env.LOOPLING_API_KEY}` },
})
const me = await res.json() // { key: { name, ink_limit, ink_charged }, ink: { balance, … } }

Python

res = requests.get(
    "https://loopling.ai/v1/me",
    headers={"Authorization": f"Bearer {os.environ['LOOPLING_API_KEY']}"},
)
me = res.json()  # {"key": {"name": …, "ink_limit": …}, "ink": {"balance": …}}

GET /v1/me returns the account and key the bearer resolves to:

{
  "object": "account",
  "user_id": "usr_…",
  "key": {
    "id": "key_…",
    "prefix": "lpl_a1b2c3d4",
    "name": "staging renderer",
    "ink_limit": 5000,
    "ink_charged": 1290,
    "ink_reserved": 0
  },
  "ink": {
    "balance": 8716,
    "period_grant_remaining": 6200,
    "payg_balance": 2516,
    "usd_payg_equivalent": 87.16
  }
}

ink.balance is the period grant plus pay-as-you-go; key.ink_reserved is what this key's running requests currently hold. usd_payg_equivalent is the balance at $1 = 100 Ink.

Base URL and conventions

  • Base URL: https://loopling.ai/v1. HTTPS only.
  • Request bodies are JSON with Content-Type: application/json; any other content type answers 415 unsupported_media_type, and a body that is not JSON answers 400 invalid_json. Video bodies may be up to 64 KiB, text bodies up to 4 MiB; above that, 413 payload_too_large.
  • Responses are JSON. Streaming endpoints send text/event-stream. Every /v1 response carries Cache-Control: no-store.
  • Every /v1 response carries an X-Request-Id header; the same value appears as error.request_id on failures. Quote it when you write to support. Non-streaming text responses add X-Loopling-Ink-Charged and X-Loopling-Request-Id (the id of the request record shown in Settings → API).
  • Identifiers are opaque strings with a type prefix: vgen_… for video generations, key_… for keys.
  • Timestamps are ISO 8601 in UTC.
  • Field names follow the protocol of the endpoint: snake_case everywhere, as the OpenAI and Anthropic SDKs expect.
  • An unknown /v1 path answers a JSON 404 not_found, never an HTML page. When the API is not open on a deployment, every /v1 path answers 404 external_api_disabled.

GET /v1/models is public — no key needed — and lists every model with its live price, constraints and availability. GET /v1/models/{id} returns one card; aliases resolve (/v1/models/astra). The catalogue is generated from the same pricing contract the ledger charges from.

curl

# The catalogue is public: no key needed.
curl https://loopling.ai/v1/models

Node

// The catalogue is public: no key needed.
const res = await fetch('https://loopling.ai/v1/models')
const { data } = await res.json()
for (const model of data) console.log(model.id, model.type, model.available, model.pricing)

Python

import requests

# The catalogue is public: no key needed.
res = requests.get("https://loopling.ai/v1/models")
for model in res.json()["data"]:
    print(model["id"], model["type"], model["available"], model["pricing"])

The list response, one video card and one text card shown:

{
  "object": "list",
  "data": [
    {
      "id": "seedance-2.5",
      "object": "model",
      "type": "video",
      "label": "Seedance 2.5",
      "endpoint": "/v1/video/generations",
      "available": true,
      "pricing": {
        "unit": "second",
        "ink_per_second": { "480p": 10.2, "720p": 22.9, "1080p": 56.8 },
        "usd_payg_per_second": { "480p": 0.102, "720p": 0.229, "1080p": 0.568 },
        "ink_per_5_second_clip": { "480p": 52, "720p": 115, "1080p": 284 },
        "note": "Billed once per clip on the delivered seconds, rounded up to the whole Ink. 1 Ink = $0.01 pay-as-you-go."
      },
      "constraints": {
        "resolutions": ["480p", "720p", "1080p"],
        "aspect_ratios": ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "adaptive"],
        "resolution_ceiling_by_aspect": { "9:16": "720p", "adaptive": "720p" },
        "duration_seconds": { "min": 4, "max": 30 },
        "references": { "total": 50, "images": 30, "videos": 10, "audio": 10 }
      }
    },
    {
      "id": "gpt-6-astra",
      "object": "model",
      "type": "text",
      "aliases": ["astra"],
      "label": "Astra",
      "vendor_label": "GPT 6 Astra",
      "endpoint": "/v1/responses",
      "protocol": "openai-responses",
      "available": false,
      "pricing": {
        "unit": "1M tokens",
        "input": { "ink": 375, "usd_payg": 3.75 },
        "output": { "ink": 1875, "usd_payg": 18.75 },
        "cache_read": { "ink": 38, "usd_payg": 0.38 },
        "cache_write": { "ink": 469, "usd_payg": 4.69 },
        "note": "Ink is reserved for the prompt plus the output cap and settled to the usage the vendor reports. 1 Ink = $0.01 pay-as-you-go.",
        "long_context": {
          "above_input_tokens": 272000,
          "input": { "ink": 750, "usd_payg": 7.50 },
          "output": { "ink": 2813, "usd_payg": 28.13 },
          "cache_read": { "ink": 75, "usd_payg": 0.75 }
        }
      },
      "constraints": {
        "max_input_tokens": 922000,
        "default_max_output_tokens": 32768,
        "max_output_tokens": 128000,
        "images": false,
        "streaming": true
      }
    }
  ]
}

A video card prices per second, keyed by resolution, and adds the 5-second clip figure; a text card prices per million tokens for input, output, cache_read and cache_write — each with the Ink figure and its pay-as-you-go USD — plus long_context for Astra. available says whether the lane is switched on right now. The figures above are rendered from the same pricing contract the ledger charges from; the response you receive carries the same values, and so does Models and pricing.

Models and pricing

Ink per second of video, by model and resolution. Billing rounds up once per clip, never per second; the 5-second column is what a clip actually charges.

ModelResolutionInk / s5 s clip
Seedance 2.5480p10.252 ($0.52)
Seedance 2.5720p22.9115 ($1.15)
Seedance 2.51080p56.8284 ($2.84)
Seedance 2.0480p6.935 ($0.35)
Seedance 2.0720p15.176 ($0.76)
Seedance 2.01080p37.6189 ($1.89)
Seedance 2.0 Fast480p5.629 ($0.29)
Seedance 2.0 Fast720p12.161 ($0.61)

Ink per million tokens for text. Cache reads are billed at the cache-read rate; cache writes are billed at the 1-hour cache-write rate (the vendor's receipt does not say which tier a write belongs to, so the longer one applies).

ModelInputOutputCache readCache write
Astra (gpt-6-astra)3751,87538469
Claude Fable 5.1 (claude-fable-5-1)1,0635,313272,125
Claude Opus 5 (claude-opus-5)5322,657541,063
Claude Sonnet 5 (claude-sonnet-5)2131,06322425

Astra above 272,000 input tokens uses its long-context tier:

InputOutputCache readCache write
7502,81375938

Prices are published per unit in Ink. A request is billed as its Ink rate times the usage the vendor reports, rounded up once per request; 1 Ink = $0.01 pay-as-you-go. The figures above are the ones the ledger charges.

Errors

Every error is JSON with one shape:

{
  "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": "req_01J9X6K3M8Q2"
  }
}

type is the coarse family a client switches on; code is the stable machine reason inside it; param names the offending field when there is one; request_id is the X-Request-Id of the response.

HTTPtypecodeWhen
400invalid_request_errorinvalid_json, invalid_body, invalid_type, invalid_enum, too_long, too_large, and the field-specific codes on each product pageThe request does not validate; param names the field
400invalid_request_errorunsupported_parameterA field this API does not accept in v1 — every product page lists its own
400invalid_request_errorupstream_rejectedThe text vendor's gateway refused the request before running the model; the message is the vendor's; nothing charged
401authentication_errorinvalid_api_keyNo bearer, an unknown key, a revoked or expired key
402insufficient_ink_errorinsufficient_inkThe wallet cannot cover the reservation; the message names the Ink required and available
402insufficient_ink_errorkey_ink_limit_reachedThe key's Ink cap would be crossed
403permission_errortier_gatedThe account's plan does not allow video (a paid tier or pay-as-you-go Ink is needed)
404not_found_errornot_found, model_not_found, generation_not_found, result_unavailableUnknown route, model or generation; a generation another account owns
404not_found_errorexternal_api_disabledThe API is not open on this deployment
409invalid_request_errorgeneration_not_ready/content asked for before the generation finished
410not_found_errorresult_expired/content after the 7-day retention
413invalid_request_errorpayload_too_largeBody above the limit, or a text prompt above the model's input envelope
415invalid_request_errorunsupported_media_typeThe body is not application/json
429rate_limit_errorrate_limit_exceededMore than 60 requests in a minute on this key
429rate_limit_errorconcurrency_limit2 video generations already running on the account; Retry-After: 15
429rate_limit_errordaily_cap_reachedThe plan's daily generation cap; Retry-After names the wait
502provider_errorprovider_errorThe vendor refused or failed the request, or answered without a usable usage receipt
503provider_errorprovider_unavailableThe lane is not configured or not yet certified on this deployment, the vendor is unavailable or rate-limiting, or admission could not be completed — retry after Retry-After
503server_errorrate_limiter_unavailableThe limiter could not be reached; Retry-After: 5
500server_errorinternal_errorLoopling itself failed; the request id is in the body

429 and 503 carry a Retry-After header in seconds. Errors before dispatch charge nothing. A vendor failure after dispatch refunds a video reservation; a text call settles to what the vendor reports, and if the vendor never reports usage the hold is kept until it reconciles — never silently charged. See Refunds and holds.

Rate limits

  • 60 requests per minute per key, every /v1 route counted. Above it: 429 rate_limit_error with code: "rate_limit_exceeded" and Retry-After.
  • 2 video generations running at once per account. A third submit answers 429 with code: "concurrency_limit" and Retry-After: 15; poll or wait for one to finish.
  • The plan's daily generation cap applies to API video the way it applies in the app: 429 daily_cap_reached.
  • Text requests are not concurrency-limited by Loopling; each holds Ink while it runs.
  • An optional per-key Ink cap (402 key_ink_limit_reached at the cap) — see Per-key caps.

Clients should back off on 429 and 503 for at least the Retry-After value, and should never retry a 4xx other than those two without changing the request.

Billing summary

Every request reserves Ink up front and settles to the vendor's reported usage; failures before the vendor runs refund. Video reserves for the requested duration and resolution; text reserves for the prompt plus the output cap. Ink is the same wallet the app spends: the period grant first, then pay-as-you-go. The full model, with worked examples, is on Billing & Ink.

Changelog

  • v1 (2026-09) — Video generations for Seedance 2.5 / 2.0 / 2.0 Fast; Responses for Astra; Messages for Claude Fable 5.1, Opus 5 and Sonnet 5; public GET /v1/models and GET /v1/models/{id}; GET /v1/me; per-key Ink caps; Idempotency-Key on video submits; SSE streaming on both text endpoints with the trailing loopling.usage frame; X-Loopling-Ink-Charged on non-streaming text responses. Astra and Claude open after certification.
  • Not in v1: webhooks, auto duration and edit tasks on video, image inputs on text, /v1/chat/completions, organisation-scoped keys.

API overview

loopling.ai · grows with you, grows itself

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