Developer API
Guide · Video · Responses · Messages · Billing & Ink
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:
| Product | Endpoint | Protocol | Models |
|---|---|---|---|
| Video | POST /v1/video/generations | REST, async (submit → poll → download) | seedance-2.5, seedance-2.0, seedance-2.0-fast |
| Responses | POST /v1/responses | OpenAI Responses protocol | gpt-6-astra (alias astra) |
| Messages | POST /v1/messages | Anthropic Messages protocol | claude-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.
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.
402 insufficient_ink_error with code: "key_ink_limit_reached" while the account's other keys keep working.401 invalid_api_key; generations already running still finish and settle.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.
https://loopling.ai/v1. HTTPS only.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.text/event-stream. Every /v1 response carries Cache-Control: no-store./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).vgen_… for video generations, key_… for keys.snake_case everywhere, as the OpenAI and Anthropic SDKs expect./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/modelsNode
// 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.
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.
| Model | Resolution | Ink / s | 5 s clip |
|---|---|---|---|
| Seedance 2.5 | 480p | 10.2 | 52 ($0.52) |
| Seedance 2.5 | 720p | 22.9 | 115 ($1.15) |
| Seedance 2.5 | 1080p | 56.8 | 284 ($2.84) |
| Seedance 2.0 | 480p | 6.9 | 35 ($0.35) |
| Seedance 2.0 | 720p | 15.1 | 76 ($0.76) |
| Seedance 2.0 | 1080p | 37.6 | 189 ($1.89) |
| Seedance 2.0 Fast | 480p | 5.6 | 29 ($0.29) |
| Seedance 2.0 Fast | 720p | 12.1 | 61 ($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).
| Model | Input | Output | Cache read | Cache write |
|---|---|---|---|---|
Astra (gpt-6-astra) | 375 | 1,875 | 38 | 469 |
Claude Fable 5.1 (claude-fable-5-1) | 1,063 | 5,313 | 27 | 2,125 |
Claude Opus 5 (claude-opus-5) | 532 | 2,657 | 54 | 1,063 |
Claude Sonnet 5 (claude-sonnet-5) | 213 | 1,063 | 22 | 425 |
Astra above 272,000 input tokens uses its long-context tier:
| Input | Output | Cache read | Cache write |
|---|---|---|---|
| 750 | 2,813 | 75 | 938 |
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.
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.
| HTTP | type | code | When |
|---|---|---|---|
| 400 | invalid_request_error | invalid_json, invalid_body, invalid_type, invalid_enum, too_long, too_large, and the field-specific codes on each product page | The request does not validate; param names the field |
| 400 | invalid_request_error | unsupported_parameter | A field this API does not accept in v1 — every product page lists its own |
| 400 | invalid_request_error | upstream_rejected | The text vendor's gateway refused the request before running the model; the message is the vendor's; nothing charged |
| 401 | authentication_error | invalid_api_key | No bearer, an unknown key, a revoked or expired key |
| 402 | insufficient_ink_error | insufficient_ink | The wallet cannot cover the reservation; the message names the Ink required and available |
| 402 | insufficient_ink_error | key_ink_limit_reached | The key's Ink cap would be crossed |
| 403 | permission_error | tier_gated | The account's plan does not allow video (a paid tier or pay-as-you-go Ink is needed) |
| 404 | not_found_error | not_found, model_not_found, generation_not_found, result_unavailable | Unknown route, model or generation; a generation another account owns |
| 404 | not_found_error | external_api_disabled | The API is not open on this deployment |
| 409 | invalid_request_error | generation_not_ready | /content asked for before the generation finished |
| 410 | not_found_error | result_expired | /content after the 7-day retention |
| 413 | invalid_request_error | payload_too_large | Body above the limit, or a text prompt above the model's input envelope |
| 415 | invalid_request_error | unsupported_media_type | The body is not application/json |
| 429 | rate_limit_error | rate_limit_exceeded | More than 60 requests in a minute on this key |
| 429 | rate_limit_error | concurrency_limit | 2 video generations already running on the account; Retry-After: 15 |
| 429 | rate_limit_error | daily_cap_reached | The plan's daily generation cap; Retry-After names the wait |
| 502 | provider_error | provider_error | The vendor refused or failed the request, or answered without a usable usage receipt |
| 503 | provider_error | provider_unavailable | The 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 |
| 503 | server_error | rate_limiter_unavailable | The limiter could not be reached; Retry-After: 5 |
| 500 | server_error | internal_error | Loopling 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.
/v1 route counted. Above it: 429 rate_limit_error with code: "rate_limit_exceeded" and Retry-After.429 with code: "concurrency_limit" and Retry-After: 15; poll or wait for one to finish.429 daily_cap_reached.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.
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.
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.auto duration and edit tasks on video, image inputs on text, /v1/chat/completions, organisation-scoped keys.loopling.ai · grows with you, grows itself
hello@loopling.ai · community · pricing · Enterprise · API · privacy · terms