# Kubeez REST API > Public HTTP API for AI media generation: image, video, music, speech, > captions, audio separation. Authenticate with API keys (`sk_live_...`). > Same models, capabilities, and billing as the Kubeez web app and MCP server. ## Public, no-auth discovery URLs - https://api.kubeez.com/llms.txt — this file - https://api.kubeez.com/docs/models.json — live catalog of every enabled model - https://api.kubeez.com/docs/models/{model_id}.json — one model by id - https://api.kubeez.com/openapi.json — OpenAPI 3.1 spec - https://api.kubeez.com/health — liveness probe ## Authentication Send one of these headers on every request to `/v1/*`: ``` X-API-Key: sk_live_... Authorization: Bearer sk_live_... ``` Create a key at https://kubeez.com/api. Missing scope returns HTTP 403. NEVER put the API key in a URL query string — only in headers. ## Invalid input is rejected, never coerced This API does not silently adjust a parameter it cannot honour. An invalid value returns 400 with `error`, `message`, `param` and `valid_values`, and NO credits are spent. If a parameter is fixed by the model id (video resolution and quality tiers), sending it returns `param_ignored_use_variant`, whose `valid_values` lists the concrete model ids to use instead. Do not confuse that with `variant_required`, a different 400 with a different body: it means the `model` you named is a FAMILY, not a concrete model id, and it carries `family`, `available_variants` and `recommended_variant` instead of `param` / `valid_values`. Handle both. ## Endpoints (generated from the live route table) - `GET /v1/assets` — List the user's Asset Library - `POST /v1/assets` — Save a remote URL as a named asset - `DELETE /v1/assets/{id}` — Delete an asset - `PATCH /v1/assets/{id}` — Rename an asset - `GET /v1/balance` — Account credit balance - `POST /v1/generate/captions` — Caption a video - `POST /v1/generate/dialogue` — Text-to-speech (single voice) - `POST /v1/generate/media` — Start media generation - `POST /v1/generate/media/extend` — Extend a Veo 3.1 video - `GET /v1/generate/media/{id}` — Media generation status - `POST /v1/generate/music` — Start music generation - `GET /v1/generate/music/{id}` — Music generation status - `POST /v1/generate/remove-background` — Remove an image background - `POST /v1/generate/separation` — Separate audio into stems - `GET /v1/generate/separation/{id}` — Separation status - `GET /v1/generations` — List generations (history) - `GET /v1/models` — List models - `GET /v1/premium-video/orders/{order_id}` — Premium video order status - `POST /v1/premium-video/questions/{question_id}/answer` — Answer a premium video design question - `POST /v1/story/generate` — Generate a long-form story video - `GET /v1/story/{job_id}` — Story video job status - `POST /v1/upload/media` — Upload input media ## Canonical workflows for an automating agent ### A. Pick a model, then generate, then poll ```bash # 1. Find an enabled model. curl -s https://api.kubeez.com/docs/models.json | jq '.models[] | select(.model_type=="image") | .model_id' | head # 2. Start the job. curl -s -X POST https://api.kubeez.com/v1/generate/media \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{"model":"nano-banana-2","prompt":"matte-black headphones on marble, soft studio light","aspect_ratio":"1:1"}' # → {"generation_id":"abc-123-uuid","status":"queued"} # 3. Poll until done. First poll: ~5-10s for images, ~30-60s for videos. curl -s https://api.kubeez.com/v1/generate/media/abc-123-uuid \ -H "X-API-Key: sk_live_..." # → {"id":"...", "status":"completed", "outputs":[{"url":"https://...","media_type":"image"}]} ``` A correct polling loop: backoff `[5s, 5s, 5s, 10s, 10s, 15s, 15s, 30s, 30s, ...]` capped at 30s. Stop on `status` ∈ `completed`, `failed`, `cancelled`. Most images finish in 10-30s; videos in 30-180s; music in 60-180s; separation in 60-300s. ### B. User has a local file → upload, then use it as a reference ```bash # 1. Upload (max 500 MB). curl -s -X POST https://api.kubeez.com/v1/upload/media \ -H "X-API-Key: sk_live_..." \ -F "file=@./photo.jpg" # → {"success":true,"urls":["https://storage.../photo.jpg"],"uploaded":1} # 2. Use the returned URL as source_media_urls. curl -s -X POST https://api.kubeez.com/v1/generate/media \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{"model":"nano-banana-2","prompt":"replace background with sunset","aspect_ratio":"1:1","source_media_urls":["https://storage.../photo.jpg"],"generation_type":"image-to-image"}' ``` ### C. Reuse a brand asset across many generations (logo, recurring character, voiceover) ```bash # 1. Save once. curl -s -X POST https://api.kubeez.com/v1/assets \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{"name":"acme-logo","url":"https://cdn.example.com/logo.png"}' # → {"asset":{"id":"a1b2c3","name":"acme-logo","url":"https://cdn.kubeez.com/...signed..."}} # 2. Reuse forever via /v1/assets — each fetch returns a freshly signed URL. curl -s https://api.kubeez.com/v1/assets -H "X-API-Key: sk_live_..." | jq '.assets[] | select(.name=="acme-logo") | .url' ``` The signed `url` field is good for 1 hour. Re-list when it expires (`url_expires_at`); the bytes themselves are persistent. ### D. Multi-speaker TTS ```bash curl -s -X POST https://api.kubeez.com/v1/generate/dialogue \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{"text":"Welcome to Kubeez. Let me show you around.","voice":"Rachel","stability":0.5,"language_code":"en"}' # → poll /v1/generate/media/{generation_id} (yes — dialogue uses the /media/ status path) ``` Voices are not fixed here — read the current, authoritative list from `/docs/models.json` → `text-to-dialogue-v3` → `voice_allowlist` before picking one. ## Model-specific parameters `/docs/models.json` is authoritative — read each model's `capabilities` before sending the body. Common fields: - `aspect_ratio_options` — pick one. Some models reject `auto` / `1:1` at higher resolution tiers (gpt-image-2 at 2K/4K). - `duration_options` — videos only. Some are flexible integer ranges (Kling 3.0: any 3-15s); most are presets. - `resolution_options` — `1K|2K|4K` for image-tier models that have it; `480p|720p|1080p` for video. - `quality_options` — `basic|high` (Seedream v4.5, 5-lite) or `fast|standard|ultra` (Imagen 4) or `720p|720p-draft|1080p|1080p-draft` (P-Video). - `max_input_images`, `max_input_videos`, `max_input_audios` — limits on `source_media_urls` per type. - `supports_sound` / `video_audio` — sound is `toggle_via_sound_param`, `included` (free), or `silent`. - `supports_negative_prompt` — boolean. - `prompt_max_chars` — cap on the `prompt` field. ## Tips for AI agents 1. Always read `/docs/models.json` first. The model catalog changes — don't memorize fields from this file; treat it as the source of truth. 2. For one-off automation, fetch `/docs/models/{model_id}.json` instead of the full catalog — same data, ~99% smaller payload. 3. Status polling: media + dialogue both poll `/v1/generate/media/{id}` (only music + separation have their own status routes). Don't poll faster than every 5s for the first 30s, then every 15-30s. 4. Prefer URL ingest (`POST /v1/assets` / passing public URLs in `source_media_urls`) over multipart uploads when the file is already on the public web. 5. Cross-origin: every public route sends permissive CORS headers; you can call from a browser. 6. The `/docs` page is a static SPA bundle served directly by nginx (not generated per-request) — its content still renders client-side, so a plain `curl` only sees the HTML shell, not model data. Use `/docs/models.json` and `/openapi.json` for machine ingestion; use this `/llms.txt` for narrative guidance. ## Errors - `400 invalid_request` — schema or value mismatch (read `message` and `param`) - `400 unsupported_aspect` — aspect_ratio not valid for this model - `400 param_ignored_use_variant` — the param is fixed by the model id and would be ignored; `valid_values` lists the concrete model ids to use instead - `400 variant_required` — `model` is a family name, not a concrete model id; body carries `family`, `available_variants`, `recommended_variant` - `400 unsupported_quality` / `unsupported_resolution` — value not in this model's declared tier list - `400 prompt_too_long` / `too_many_input_images` — declared per-model limit exceeded - `400 insufficient_credits` — every generation route returns this as a plain 400, same envelope as every other rejection above: `/v1/generate/media`, `/v1/generate/music`, `/v1/generate/dialogue`, `/v1/generate/separation`, `/v1/generate/remove-background`, `/v1/generate/captions`. Top up at https://kubeez.com/credits, then retry the exact same request. - `401` — bad or missing API key - `402 insufficient_credits` — the ONE exception: `POST /v1/generate/media/extend` only. Same fix (top up, then retry). - `403 missing_scope` — the key lacks the required scope - `404 model_not_found` / `not_found` - `409 name_taken` — POST /v1/assets name collision - `413 file_too_large` / `quota_exceeded` - `429 rate_limit_exceeded` — body carries `limit`, `remaining`, `reset_after` - `5xx` — server side; safe to retry idempotently after a delay `insufficient_credits` is retryable after a top-up no matter which status code carried it — key off the `error` field in the body, not the HTTP status alone. Every other 4xx is NOT retryable except 429 — fix the body first. ## Rate limits (per API key, requests/minute) - Generate media: 30 - Generate music: 10 - Generate dialogue: 10 - Upload media: 30 - All read endpoints: 120 ## Cost Read `cost_per_generation` and `cost_note` from /docs/models.json. Flat per-generation for most images, music, dialogue and separation; per-second for most videos. Kubeez does not refund failed or unsatisfactory generations. ## Related - MCP server: https://mcp.kubeez.com/docs — same models as MCP tools - Web app: https://kubeez.com