npx skills add ...
npx skills add letz-ai/letzai-skill --skill letzai-api
Generate AI images and videos via the LetzAI API. Images with Nano Banana Pro, Seedream 5, Flux 2, GPT Image 2; videos with Veo 3.1, Kling V3, Seedance 2.0. Includes context editing, upscaling, asset uploads, and custom @model training. Use for content creation apps and automation.
npx skills add letz-ai/letzai-skill --skill letzai-api
Helps you integrate the LetzAI public API for AI image and video generation, editing,
upscaling, asset uploads, and custom model (LoRA) training. Custom-trained models for
persons, objects and styles are referenced inside prompts with @modelname.
https://api.letz.aiAuthorization: Bearer YOUR_API_KEY/doc-json and /doc-yamlDo not confuse the public API with the private one.
https://api.letz.aiis the public developer API described here.https://private-api.letz.aipowers the LetzAI web app and has different route names (/upscales,/image_completions,/video-edits). Those routes 404 on the public API.
The API validates request bodies strictly. Sending a field that is not in the schema
returns 400. In particular there is no negativePrompt, seed or aspectRatio on
POST /images — aspect ratio is expressed via width/height.
POST /images| Parameter | Type | Default | Notes |
|---|---|---|---|
prompt | string | — | Required. May contain @modelname tags. |
baseModel | string | account default | See model table below. |
mode | string | model default | Resolution tier — allowed values differ per model. |
width | int | 1600 | 480–2160 |
height | int | 1600 | 480–2160 |
quality | int | 2 | 1–6 |
creativity | int | 2 | 1–6 |
hasWatermark | bool | true | |
systemVersion | int | account default | 2 or 3 |
hideFromUserProfile | bool | false | |
webhookUrl | string | — | POSTed when the job finishes. |
organizationId | UUID | — | Bill credits to an org you belong to. |
mode is the resolution tier, and the accepted values are model-specific.
| Model | baseModel | mode values | Credits (generate) | Credits (edit) |
|---|---|---|---|---|
| Nano Banana Pro (Google) | gemini-3-pro-image | default (1K) · 2k (HD) · 4k | 80 / 160 / 240 | 80 / 160 / 240 |
| Nano Banana 2 (Google) | nbf-inferencesh | default (1K) · 2k (HD) · 4k | 40 / 80 / 160 | 50 / 100 / 150 |
| Nano Banana 2 Lite (Google) | nano-banana-2-lite | default (1K) | 20 | 25 |
| Seedream 5 Pro (ByteDance) | seedream-5-0-pro | 2k (HD) · 4k | 80 / 160 | 80 / 160 |
| Seedream 4.5 (ByteDance) | seedream-4-5-251128 | 2k (HD) · 4k | 80 / 160 | 80 / 160 |
| Flux 2 (Black Forest Labs) | flux2 | 1k · hd | 60 / 120 | 60 / 120 |
| GPT Image 2 (OpenAI) | gpt-image-2 | 1k · hd · 4k | 160 / 240 / 480 | 160 / 240 / 480 |
| WAN 2.7 Image Pro (Alibaba) | wan-2-7-image-pro | 1k · 2k | 10 | 10 |
Aliases are accepted, e.g. gemini-3-pro-image-preview still resolves to Nano Banana Pro.
The live catalogue with pricing lives at letz.ai/docs/models.json.
Workflow
POST /imagesid from the responseGET /images/{id} every 3 sstatus === "ready", read imageVersions.originalSee examples/image_generation.js.
POST /videos| Parameter | Type | Notes |
|---|---|---|
prompt | string | Required. |
originalImageCompletionId | UUID | Animate a previously generated LetzAI image. |
imageUrl | string | Animate a public image URL. |
imageUrls | string[] | Multi-frame: [0] = first frame, [1] = last frame. |
prompts | object[] | Multi-shot storyboards (video-kling3): [{ prompt, duration }]. |
baseModel | string | Optional; settings.mode is the usual selector. |
width / height / resolution | number | Optional output sizing. |
settings | object | Model + duration + audio (below). |
webhookUrl | string | |
hidePrompt | bool | |
organizationId | UUID |
Image input is optional — omit all of originalImageCompletionId / imageUrl /
imageUrls for pure text-to-video.
settings| Field | Notes |
|---|---|
mode | Video model key — see table. |
duration | Seconds, clamped to the model's allowed values. |
withSound | Enable native audio where supported. |
highQuality | Higher-resolution tier; usually doubles the price. |
resolution | "720p", "1080p", "4k" for per-second-priced models. |
Use the video- prefixed key as settings.mode.
| Model | settings.mode | Duration | Credits |
|---|---|---|---|
| Google Veo 3.1 | video-veo31 | 8 s (fixed) | 1500 · ×2 with audio · ×2 at 1080p |
| Kling V3 | video-kling3 | 3–15 s | 150 · 300 with audio |
| Kling V2.6 | video-kling26 | 5 or 10 s | 150 · ×2 with audio · ×2 at 1080p |
| WAN 2.5 | video-wan25 | 5 or 10 s | 110 · ×2 at 1080p (image-to-video only) |
| Seedance 2.0 | video-seedance2 | 4–15 s | per second: 105 @480p · 210 @720p · 500 @1080p · 1000 @4K (audio always on) |
| Seedance 2.0 Enterprise | video-seedance2-enterprise | 4–15 s | same as Seedance 2.0; requires organizationId |
| Gemini Omni Flash | video-gemini-omni | 3–10 s | 100 per second (audio always on) |
Workflow
POST /videosGET /videos/{id} every 2–3 sstatus === "ready", read videoVersions.originalSee examples/video_generation.py.
POST /image-edits| Parameter | Type | Notes |
|---|---|---|
mode | string | Required. context (AI editing) or skin (skin fix). |
prompt | string | Edit instruction. May contain @modelname tags. |
imageUrl | string | Single source image URL. |
inputImageUrls | string[] | Multi-reference source images (up to 9 in practice). |
originalImageCompletionId | UUID | Edit a previously generated LetzAI image. |
originalImageCompletionIds | UUID[] | Several LetzAI images as sources. |
baseModel | string | Same identifiers as image generation. |
settings | object | { resolution, aspect_ratio, model } |
imageCompletionsCount | int | Variations to generate (1–5, default 1). Not used by skin. |
mask | string | Base64 mask, legacy inpainting only. |
width / height | number | Target dimensions. |
webhookUrl, organizationId, hidePrompt |
settings:
resolution: "2k" (HD) or "4k" (Ultra HD)aspect_ratio: "1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21"model: same identifiers as baseModelProvide at least one source: imageUrl, inputImageUrls, originalImageCompletionId
or originalImageCompletionIds.
Prefer the top-level baseModel. Server-side queue routing reads baseModel;
settings.model is forwarded to the worker. Setting both to the same value is safest.
Workflow
POST /image-editsGET /image-edits/{id} every 3 sstatus === "ready", read generatedImageCompletion.imageVersions.originalmode: "in" (inpainting) and mode: "out" (outpainting) are deprecated — use context.
POST /upscaleThe public route is
/upscale(singular)./upscalesis the private web-app route and returns 404 here.
| Parameter | Type | Default | Notes |
|---|---|---|---|
imageId | UUID | — | A LetzAI image completion ID. |
imageUrl | string | — | Public image URL. Use instead of imageId. To upscale your own file, upload it via /user-assets and pass the returned imageUrl. |
mode | string | default | Upscaler model — see table. |
size | number | — | 2–12; output tier — see table. |
strength | number | 1 | 1–5. |
prompt | string | — | Optional guidance for creative upscalers. |
webhookUrl, organizationId |
mode | Upscaler | size → output | Credits |
|---|---|---|---|
nano-banana-pro | Google Gemini 3 Pro | 4 → 1K · 8 → 2K · 12 → 4K | 80 / 160 / 240 |
nano-banana-2 | Google Gemini 3.1 Flash | 4 → 1K · 8 → 2K · 12 → 4K | 50 / 100 / 150 |
gpt-image-2 | OpenAI GPT Image 2 | 4 → 1K · 8 → 2K · 12 → 4K | 160 / 240 / 480 |
pruna | Pruna P-Image-Upscale | 4 → 1 MP · 8 → 4 MP · 12 → 16 MP | 20 / 40 / 80 |
default | LetzAI in-house | — | 20 |
Workflow
POST /upscaleGET /upscale/{id} every 3 sstatus === "ready", read imageVersions.originalPOST /user-assetsTwo-step pre-signed S3 upload. Use this to get a public URL for a local file before
feeding it to /image-edits, /videos or /upscale.
POST /user-assets with { extension } (plus optional originalFilename,
mimeType, fileSize, caption, metadata, numberOfImages).PUT the raw file bytes to the returned uploadUrl with the matching Content-Type
header. No Authorization header — the URL is already signed.imageUrl from the step-1 response.Allowed extension values: jpg, jpeg, png, gif, webp, mp4, webm, mov,
avi, mp3, wav, ogg, m4a, aac, flac.
POST /user-images is the older image/video-only variant of the same flow; prefer
/user-assets, which also accepts audio.
See examples/uploads_and_training.md.
@modelname)Trained models for persons, objects and styles. Tag them inside any image or edit
prompt: @john_doe on the beach at sunset.
List: GET /models — query params page, limit, sortBy, sortOrder, search,
name, userId, username, class (person | object | style), type,
description, privacy (public | private | licensed), systemVersion,
isActive, status. Pagination is reported through the X-Total-Count,
X-Current-Page, X-Per-Page and X-Total-Pages response headers.
Get one: GET /models/{id}
Train a new model: POST /models — this is available over the API (Enterprise
plans).
| Parameter | Notes |
|---|---|
name | Required. Alphanumeric, _ and ., max 50 chars. Becomes the @tag. |
class | Required. person | style | object |
privacy | public | private | licensed |
type | Optional; auto-generated when omitted. |
trainingDataUrls | 1–50 public image URLs. Upload via /user-assets first. |
trainingMode | e.g. default, slow |
settings | e.g. ["NO_ADULT_CONTENT", "NO_VIOLENCE"] |
description, website, webhookUrl, organizationId | Optional. |
Training starts automatically. Poll GET /models/{id} until status is available.
Manage: PATCH /models/{id}, DELETE /models/{id},
PUT /models/{id}/thumbnail.
Create an image → pick baseModel + mode from the model table → POST /images →
poll GET /images/{id} → imageVersions.original.
Use a trained model → GET /models?class=person to find the name → put
@name in the prompt → generate normally.
Edit an image → get a source (URL, /user-assets upload, or a LetzAI completion id)
→ POST /image-edits with mode: "context" → poll → generatedImageCompletion.imageVersions.original.
Create a video → optional source image → POST /videos with
settings.mode = a video-* key → poll → videoVersions.original.
Upscale → POST /upscale with imageId or imageUrl → poll → imageVersions.original.
Train a model → upload images via /user-assets → POST /models with
trainingDataUrls → poll GET /models/{id} until available.
Every generation endpoint is asynchronous: the POST returns an id, and you poll the
matching GET until a terminal status.
| Resource | Statuses |
|---|---|
| Images | new · generating · ready · failed · interrupted · not_allowed · hidden |
| Videos | new · generating · ready · saved · failed · interrupted |
| Image edits | new · generating · ready · saved · failed · interrupted |
| Upscales | new · generating · ready · failed |
| Models | new · pending · training · finished · available · failed |
There is no "in progress" status — in-flight jobs report generating. Treat
ready and saved as success; failed, interrupted and not_allowed as terminal
failures. Responses also carry progress (0–100) and, during generation, a base64
previewImage.
Recommended intervals: 3 s for images / edits / upscales, 2–3 s for videos. Pass a
webhookUrl instead of polling for production workloads.
For a full implementation, see examples/polling_pattern.md.
| Status | Meaning | Fix |
|---|---|---|
| 400 | Invalid parameters, or an unknown field in the body | Check ranges and remove fields that are not in the schema |
| 401 | Missing/invalid key, or not a member of organizationId | Check the Authorization header |
| 402 | Insufficient credits | Top up at letz.ai/subscription |
| 404 | Resource not found — or you used a private-API route | Check the id, and that the path exists on api.letz.ai |
| 429 | Rate limited | Back off exponentially |
| 500 | Server error | Retry with backoff |
mode values are model-specific; an unsupported tier falls back to the model default.organizationId you are a member of.| Endpoint | Method | Purpose |
|---|---|---|
/images | POST | Create image |
/images | GET | List / filter images |
/images/{id} | GET | Image status + URLs |
/images/{id}/interruption | PUT | Stop generation |
/images/{id}/privacy | PUT | { privacy: "public" | "private" | "licensed" } |
/images/{id}/prompt-privacy | PUT | Hide/show the prompt publicly |
/image-edits | POST | Create edit |
/image-edits | GET | List edits |
/image-edits/{id} | GET | Edit status + URLs |
/image-edits/{imageCompletionId}/mask | GET | Mask used for a legacy inpaint |
/videos | POST | Create video |
/videos | GET | List videos |
/videos/{id} | GET | Video status + URLs |
/videos/{id}/privacy | PUT | Change video privacy |
/videos/{id}/prompt-privacy | PUT | Hide/show the prompt publicly |
/upscale | POST | Create upscale |
/upscale | GET | List upscales |
/upscale/{id} | GET | Upscale status + URLs |
/upscale/{id} | DELETE | Delete an upscale |
/models | GET | List models |
/models | POST | Train a new model |
/models/{id} | GET | Model details |
/models/{id} | PATCH | Update model metadata |
/models/{id} | DELETE | Delete model |
/models/{id}/thumbnail | PUT | Set thumbnail |
/user-assets | POST | Get a pre-signed upload URL (image/video/audio) |
/user-assets | GET | List uploads |
/user-assets/{id} | GET / PATCH / DELETE | Manage an upload |
/user-images | POST / GET | Legacy image+video-only upload flow |
/user-images/{id} | GET / DELETE | Manage a legacy upload |
/realtime/health | GET | Realtime gateway health |
Not on the public API (private web-app routes — these 404): /upscales,
/video-edits, /image_completions, PUT /videos/{id}/interruption.
imageVersions.original, imageVersions["1920x1920"], imageVersions["640x640"]generatedImageCompletion.imageVersions.original (source in originalImageCompletion)videoVersions.originalprogress (0–100), previewImage (base64), statusDetail on failureTo see the current list of AI Models that are supported on LetzAI, including their parameters and token costs, always read the models.json file:
Model catalogue (live): letz.ai/docs/models.json
/doc-jsonheaders = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['LETZAI_API_KEY']}",
}