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 | /models | Chat models, OpenAI shape. ?type=create lists the Create models, ?type=all both. |
POST | /chat/completions | Chat, streaming or not, tools, images in, reasoning effort. |
POST | /images/generations | Images, OpenAI shape. Waits for the result up to `wait` seconds. |
POST | /jobs | Any Create job: image, edit, video, animate, upscale, lipsync, extend, motion, voice, music, training, studio models. |
POST | /jobs/quote | The price of a job before it starts. Books nothing. |
GET | /jobs | Your newest jobs. |
GET | /jobs/{id} | One job: status, credits, error, result. |
POST | /jobs/{id}/cancel | Cancel a queued job. Its credits come back. |
GET | /jobs/{id}/content | The result file (a redirect to a fresh link). |
POST | /files | Upload 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.
| kind | op | needs |
|---|---|---|
| image | generate | prompt |
| image | edit | prompt, params.source_path, params.mask_path unless the model is maskless |
| image | removebg | params.source_path |
| image | eraser | params.source_path, params.mask_path |
| image | upscale | params.source_path, params.target_resolution 2k, 4k or 8k |
| video | generate | prompt, params.duration from the model's durations |
| video | animate | prompt, params.source_path (start frame), params.duration |
| video | upscale | params.source_url of one of your own video results |
| video | extend | params.source_url of one of your own video results |
| video | lipsync | params.audio_path or audio_url, and source_path or video_path (see lipsync_source) |
| video | motion | params.source_path (character), params.video_path (driving clip) |
| audio | tts | prompt (the text); a voice clone also params.audio_path |
| audio | music | prompt, optional params.duration in seconds |
| image or video | lora-train | params.image_paths, at least 4 training images |
| image, video or audio | studio | params.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.mp4Models
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
maturein Create and none of the direct chat models. A request for one is refused with 403model_blocked_by_key_policyand 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
nabove 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.
| status | code | meaning |
|---|---|---|
| 400 | invalid input, unsupported_duration, invalid_size | The request is wrong in itself. The message says which field. |
| 401 | Missing, unknown or revoked key. | |
| 402 | No active plan or credit pack on the account. | |
| 403 | model_blocked_by_key_policy | This key is set to Strict and the model is not open to it. Nothing was charged. |
| 404 | No such job on your account. | |
| 409 | quote_changed | The price is above params.max_credits. Ask for a new quote. |
| 409 | The job already runs and can no longer be canceled. | |
| 413 / 415 | The upload is larger than 20 MB, or not a format the role takes. | |
| 422 | prompt_blocked | A hard ban. These hold on every setting and every key. |
| 422 | policy_blocked | The content setting in effect does not allow this prompt. |
| 429 | rate_limited | Too many requests. Wait for the Retry-After header. |
| 429 | credits_exhausted, video_budget_exhausted | Not enough credits (or video budget) left for this job. |
| 502 | job_failed | The render failed. Its credits were returned. |
| 503 | media_not_live, model_unavailable | The service or this model is busy or down. Try again later. |