Developers

LU Cloud API

Chat, images, video, voice and music with one key. OpenAI compatible, so the OpenAI SDKs and most tools work by changing the base URL.

Start

Create a key in the app under Account, Cloud API keys. It works on every cloud plan and every credit pack and spends the same credits at the same prices as the app. It cannot read or change your account. Send it as a bearer token:

Base URL   https://lu-labs.ai/api/inference/v1
Header     Authorization: Bearer lu_...
curl https://lu-labs.ai/api/inference/v1/chat/completions \
  -H "Authorization: Bearer $LU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "moonshotai/Kimi-K3.1-Pro", "messages": [{"role": "user", "content": "Hello"}]}'
from openai import OpenAI

client = OpenAI(base_url="https://lu-labs.ai/api/inference/v1", api_key="lu_...")

reply = client.chat.completions.create(
    model="moonshotai/Kimi-K3.1-Pro",
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.choices[0].message.content)

image = client.images.generate(
    model="flux-schnell",
    prompt="a lighthouse at dawn, film photo",
    size="1024x1024",
)
print(image.data[0].url)

Endpoints

GET/modelsChat models, OpenAI shape. ?type=create lists the Create models, ?type=all both.
POST/chat/completionsChat, streaming or not, tools, images in, reasoning effort.
POST/images/generationsImages, OpenAI shape. Waits for the result up to `wait` seconds.
POST/jobsAny Create job: image, edit, video, animate, upscale, lipsync, extend, motion, voice, music, training, studio models.
POST/jobs/quoteThe price of a job before it starts. Books nothing.
GET/jobsYour newest jobs.
GET/jobs/{id}One job: status, credits, error, result.
POST/jobs/{id}/cancelCancel a queued job. Its credits come back.
GET/jobs/{id}/contentThe result file (a redirect to a fresh link).
POST/filesUpload an input: a picture, mask, clip, speech or training image, up to 20 MB.

Chat

POST /chat/completions takes the OpenAI body and forwards the knobs it does not know. Every chat model of the app answers here, the direct models included. Each model keeps its own sampling range: a temperature or top_p beyond what the model handles well is brought into its range before the request goes out. An agent step that starts repeating itself is stopped, and the repeated part is not charged. Credits are reserved when a request starts and settled to the real use when it ends.

Examples: Kimi K3.1 Pro (moonshotai/Kimi-K3.1-Pro), DeepSeek V4.1 Flash 1017 (deepseek-ai/DeepSeek-V4.1-Flash-1017), GLM 5.3 (zai-org/GLM-5.3), Qwen 3.8 A95B (Qwen/Qwen3.8-2.4T-A95B). The full list with context window, tools, vision and credit rates is GET /models.

Images

POST /images/generations is the OpenAI image endpoint: model, prompt, n up to 4, size as WIDTHxHEIGHT, response_format url or b64_json. Our own fields: negative_prompt, seed, wait (seconds to wait for the result, default 50, 0 to return at once) and options for a studio image model. Done in time: 200 with data. Still rendering: 202 with an empty data and the jobs to poll. Every answer carries jobs.

Examples: Flux Schnell (fast) (flux-schnell), Flux Dev (quality) (flux-dev), Flux 2 Dev (flux-2-dev).

Every other Create job

POST /jobs takes the body the Create tab sends: { "kind", "model", "prompt", "params": { "op", ... } }. It runs the same checks, prices and credit reservation as the app and answers 202 with the job. Poll GET /jobs/{id} until status is succeeded, failed or canceled. A failed or canceled job gives its credits back. Files go up first through POST /files (multipart file and role: source, mask, train, audio or video) and are named by the returned path. For removebg, eraser and upscale the model may be left out.

kindopneeds
imagegenerateprompt
imageeditprompt, params.source_path, params.mask_path unless the model is maskless
imageremovebgparams.source_path
imageeraserparams.source_path, params.mask_path
imageupscaleparams.source_path, params.target_resolution 2k, 4k or 8k
videogenerateprompt, params.duration from the model's durations
videoanimateprompt, params.source_path (start frame), params.duration
videoupscaleparams.source_url of one of your own video results
videoextendparams.source_url of one of your own video results
videolipsyncparams.audio_path or audio_url, and source_path or video_path (see lipsync_source)
videomotionparams.source_path (character), params.video_path (driving clip)
audiottsprompt (the text); a voice clone also params.audio_path
audiomusicprompt, optional params.duration in seconds
image or videolora-trainparams.image_paths, at least 4 training images
image, video or audiostudioparams.studio_options and the files named in the model's inputs

Video examples: Wan 2.2 720p (wan-2.2-720p), Wan 2.2 Fast (wan-2.2-fast), LTX-2 (with audio) (ltx-2). A price that depends on the inputs (studio models, clip length, speech length) is best asked for with POST /jobs/quote; send the figure as params.max_credits and the job never books more. params.client_request_id (a UUID) makes a retry safe: the same id returns the job it already booked.

# 1. upload the start frame
curl https://lu-labs.ai/api/inference/v1/files -H "Authorization: Bearer $LU_API_KEY" \
  -F role=source -F file=@frame.png
# -> {"path": "<your-id>/<uuid>.png", ...}

# 2. price it, then start it with that price as the ceiling
curl https://lu-labs.ai/api/inference/v1/jobs/quote -H "Authorization: Bearer $LU_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "video", "model": "wan-2.2-720p", "prompt": "the waves roll in",
       "params": {"op": "animate", "source_path": "<path>", "duration": 5}}'
# -> {"object": "job.quote", "credits": 1234, ...}

curl https://lu-labs.ai/api/inference/v1/jobs -H "Authorization: Bearer $LU_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "video", "model": "wan-2.2-720p", "prompt": "the waves roll in",
       "params": {"op": "animate", "source_path": "<path>", "duration": 5, "max_credits": 1234}}'
# -> 202 {"object": "job", "id": "...", "status": "queued", "credits": 1234}

# 3. poll until status is succeeded, then fetch the file
curl https://lu-labs.ai/api/inference/v1/jobs/<id> -H "Authorization: Bearer $LU_API_KEY"
curl -L https://lu-labs.ai/api/inference/v1/jobs/<id>/content -H "Authorization: Bearer $LU_API_KEY" -o clip.mp4

Models

GET /models lists the chat models, the way OpenAI clients expect. GET /models?type=create lists every Create model with kind, ops, credits (base price, per duration, per second or per length), durations for video, mature, and for a studio model its inputs and options; tools prices removebg, eraser and upscale. ?type=all returns both in one list, each row with its type. A key only sees the models it may use.

Content setting per key

Every key has a content setting: Strict, Soft or Off, the same three as the account setting under Settings, Content policy. A new key follows the account. You can set a key stricter than the account, never wider: a key on Off counts only while the account itself is on Off, with the age confirmation that takes. Change it under Account, Cloud API keys.

  • Strict: no model marked mature in Create and none of the direct chat models. A request for one is refused with 403 model_blocked_by_key_policy and nothing is charged.
  • Soft: every model; Create prompts follow the soft rule of the account setting.
  • Off: every model and no prompt rule beyond the hard bans.
  • The hard bans hold on every setting and every key, in chat and in Create: material that abuses or exploits minors is never made, and a request for it is refused with 422 prompt_blocked.

Limits

  • Chat: 60 requests per minute per account.
  • Create jobs: 30 per minute per key, 60 per minute across all keys of an account. Each image of a run with n above 1 counts.
  • Quotes: 60 per minute per key. Uploads: 60 per minute per account, 20 MB per file.
  • Up to 5 active keys per account. A 429 carries Retry-After in seconds.
  • Your plan's credits are the budget for all of it. Results stay for 7 days.

Errors

Every error is JSON with a plain English error sentence and, where useful, a code.

statuscodemeaning
400invalid input, unsupported_duration, invalid_sizeThe request is wrong in itself. The message says which field.
401Missing, unknown or revoked key.
402No active plan or credit pack on the account.
403model_blocked_by_key_policyThis key is set to Strict and the model is not open to it. Nothing was charged.
404No such job on your account.
409quote_changedThe price is above params.max_credits. Ask for a new quote.
409The job already runs and can no longer be canceled.
413 / 415The upload is larger than 20 MB, or not a format the role takes.
422prompt_blockedA hard ban. These hold on every setting and every key.
422policy_blockedThe content setting in effect does not allow this prompt.
429rate_limitedToo many requests. Wait for the Retry-After header.
429credits_exhausted, video_budget_exhaustedNot enough credits (or video budget) left for this job.
502job_failedThe render failed. Its credits were returned.
503media_not_live, model_unavailableThe service or this model is busy or down. Try again later.