
Letzai Api
- 908 installs
- Updated July 28, 2026
- letz-ai/letzai-skill
letzai-api is an agent skill that integrates LetzAI image and video generation, editing, upscaling, and custom @modelname models through the LetzAI REST API for developers building content automation in Cursor or Claude
About
letzai-api is an MIT-licensed agent skill from letz-ai/letzai-skill that enables coding agents to integrate the LetzAI API for AI-powered image and video workflows. Image models include Nano Banana Pro, Flux2 Max, and SeeDream; video models include VEO and Kling. Developers can invoke custom-trained models with @modelname syntax for persons, objects, or styles, plus context editing and upscaling endpoints. Authentication uses the LetzAI base URL at https://api.le... with dependencies on node-fetch for npm and requests for pip. Reach for letzai-api when wiring generative media into content apps or agent automations rather than building models locally. The skill covers API integration patterns, not hosting or model training infrastructure.
- Generates images using Nano Banana Pro, Flux2 Max, and SeeDream models
- Creates videos with VEO and Kling models
- Supports custom trained models via @modelname syntax
- Includes context editing and image upscaling capabilities
- Works with both JavaScript (node-fetch) and Python (requests) implementations
Letzai Api by the numbers
- 908 all-time installs (skills.sh)
- +2 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #300 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/letz-ai/letzai-skill --skill letzai-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 908 |
|---|---|
| Security audit | 1 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | letz-ai/letzai-skill ↗ |
How do you integrate LetzAI image and video API?
Let their coding agent generate, edit, and upscale AI images and videos through the LetzAI API inside Cursor or Claude Code sessions.
Who is it for?
Developers adding LetzAI generative image and video endpoints to Node.js or Python apps inside agent-assisted coding sessions.
Skip if: Teams training custom diffusion models locally or building non-LetzAI provider integrations without the LetzAI REST API.
When should I use this skill?
The user asks to generate, edit, upscale AI images or videos via LetzAI, Flux2 Max, VEO, Kling, or @modelname custom models.
What you get
Working LetzAI API client code, authenticated requests, and image or video generation, edit, and upscale call patterns.
- API integration code
- Image and video generation call patterns
By the numbers
- Documents 3 image models: Nano Banana Pro, Flux2 Max, and SeeDream
- Documents 2 video models: VEO and Kling
- MIT license with 2 declared dependencies: node-fetch and requests
Files
LetzAI API Integration Skill
Overview
This skill enables Claude to help users integrate with the LetzAI API for AI-powered image and video generation, editing, and upscaling. Users can also leverage custom-trained AI models (persons, objects, styles) via the @modelname syntax.
Authentication
- Base URL:
https://api.letz.ai - Authentication: Bearer token in Authorization header
- Get API Key: letz.ai/subscription
- API Documentation: api.letz.ai/doc
Setting Up Authentication
const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
};headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
}Core Workflows
1. Image Generation
Endpoint: POST /images
Required Parameters:
prompt(string): Text description of the desired image. Can include@modelnameto use trained models.
Optional Parameters:
baseModel: AI model to use"gemini-3-pro-image-preview"- Nano Banana Pro (recommended)"flux2-max"- Flux2 Max"seedream-4-5-251128"- SeeDream 4.5mode: Resolution mode (varies by model)- Nano Banana Pro:
"default","2k","4k" - Flux2 Max:
"1k","hd" - SeeDream:
"2k","4k" width/height: Image dimensions (520-2160px)
Workflow: 1. POST to /images with parameters 2. Receive id in response 3. Poll GET /images/{id} every 3 seconds 4. When status === "ready", access imageVersions.original
For code examples, see examples/image_generation.js
2. Video Generation
Endpoint: POST /videos
Required Parameters:
prompt(string): Text description of the desired video- Source image (one of):
imageUrl: URL of source imageoriginalImageCompletionId: ID from previous image generation
Optional Parameters:
settings.mode: Video model"default"- Default model"veo31"- VEO 3.1"kling26"- Kling 2.6"wan25"- Wan 2.5settings.duration: Video length in seconds (2-12 depending on model)
Workflow: 1. Ensure you have a source image (generate one first if needed) 2. POST to /videos with parameters 3. Receive id in response 4. Poll GET /videos/{id} every 2-3 seconds 5. When status === "ready", access videoPaths
For code examples, see examples/video_generation.py
3. Image Editing (Context Editing)
Endpoint: POST /image-edits
Required Parameters:
mode: Edit mode"context"- AI editing (primary mode)"skin"- Skin fixprompt: Edit instruction (e.g., "change background to beach")- Source image (one of):
imageUrl: URL of source imageinputImageUrls[]: Array of source image URLs (max 9)originalImageCompletionId: ID of previously generated LetzAI image
Optional Parameters:
settings.model:"gemini-3-pro-image-preview","flux2-max","seedream-4-5-251128"settings.resolution:"2k"(HD) or"4k"(Ultra HD)settings.aspect_ratio:"1:1","16:9","9:16","4:3","3:4","21:9","9:21"baseModel: Alternative to settings.modelwebhookUrl: Optional callback URLorganizationId: Optional org ID for billing
Workflow: 1. POST to /image-edits with parameters 2. Receive id in response 3. Poll GET /image-edits/{id} every 3 seconds 4. When status === "ready", access generatedImageCompletion.imageVersions.original
Note: Inpainting (mode: "in") and Outpainting (mode: "out") are deprecated - use Context Editing instead.
4. Image Upscaling
Endpoint: POST /upscales
Required Parameters:
- Source image (one of):
imageUrl: URL of source imageimageCompletionId: ID from previous image generation
Optional Parameters:
strength: Upscale factor (1-3)
Workflow: 1. POST to /upscales with parameters 2. Receive id in response 3. Poll GET /upscales/{id} every 3 seconds 4. When status === "ready", access upscaled image
5. Custom AI Models (Trained Models)
LetzAI users can train custom AI models on persons, objects, or styles via the web interface. These trained models can be used in prompts via the @modelname syntax.
List Models Endpoint: GET /models
Query Parameters:
page: int (default: 1)limit: int (default: 10)sortBy:"createdAt"|"usages"sortOrder:"ASC"|"DESC"class:"person"|"object"|"style"
Get Model Details: GET /models/{id}
Model Classes:
person: Trained on photos of a specific personobject: Trained on product/object imagesstyle: Trained on artistic style examples
Using Models in Prompts: Tag models with @modelname syntax:
@john_doe on the beach at sunset- Use a person modelA product photo featuring @my_product- Use an object modelPortrait in @vintage_style aesthetic- Use a style model
Note: Model training is done via the LetzAI web interface (letz.ai), not via API.
Workflow Decision Tree
User wants to create an image:
1. Determine appropriate model based on quality/cost needs 2. Use POST /images with appropriate baseModel 3. If using a trained model, include @modelname in the prompt 4. Poll GET /images/{id} every 3s until ready 5. Return imageVersions.original URL
User wants to use a custom trained model:
1. Use GET /models to list available trained models (filter by class if needed) 2. Include @modelname in the prompt when generating images 3. Generate image normally with POST /images
User wants to edit an existing image:
1. Obtain source image URL, inputImageUrls array, or originalImageCompletionId 2. Use POST /image-edits with mode="context" 3. Include settings for resolution, aspect_ratio, and model as needed 4. Poll GET /image-edits/{id} every 3s until ready 5. Return generatedImageCompletion.imageVersions.original
User wants to create a video:
1. Ensure they have a source image (URL or imageCompletionId) 2. If no source image, generate one first using /images 3. Use POST /videos with desired settings 4. Poll GET /videos/{id} every 2-3s until ready 5. Return video URL from videoPaths
User wants to upscale an image:
1. Obtain source image URL or imageCompletionId 2. Use POST /upscales with desired strength 3. Poll GET /upscales/{id} every 3s until ready 4. Return upscaled image URL
Status Polling Pattern
LetzAI uses asynchronous generation. After any POST request, you must poll the corresponding GET endpoint until the job completes.
Status Values
| Status | Meaning |
|---|---|
new | Job created, queued for processing |
in progress / generating | Currently processing |
ready | Complete - fetch URLs from response |
failed | Error occurred - check error message |
Polling Intervals
- Images: Every 3 seconds
- Videos: Every 2-3 seconds
- Image Edits: Every 3 seconds
- Upscales: Every 3 seconds
For detailed polling implementation, see examples/polling_pattern.md
Pricing Reference
| Feature | Model | Credits |
|---|---|---|
| Image Gen | Nano Banana Pro | 80/160/240 (1k/HD/4K) |
| Image Gen | Flux2 Max | 60/120 (1k/HD) |
| Image Gen | SeeDream | 80/160 (HD/4K) |
| Editing | Same as above | Same pricing |
| Video | Default | 60 cr/sec (2-6 sec) |
| Video | VEO 3.1 | 1500-6000 cr (8 sec) |
| Video | Kling 2.6 | 750-1500 cr (5-10 sec) |
| Upscale | All | 40 cr |
Error Handling
Common HTTP Status Codes
| Status | Meaning | Solution |
|---|---|---|
| 401 | Invalid or missing API key | Check Authorization header format |
| 402 | Insufficient credits | Top up at letz.ai/subscription |
| 400 | Invalid parameters | Verify baseModel, mode, dimensions |
| 404 | Resource not found | Check the ID is correct |
| 429 | Rate limited | Implement exponential backoff |
| 500 | Server error | Retry after delay |
Error Response Format
{
"error": "Error description",
"code": "ERROR_CODE"
}Limitations
- Async Generation: All generation is asynchronous - must poll for results
- Video Source: Video generation requires a source image
- Reference Images: Maximum 9 reference images for image editing
- Model Training: Cannot train custom AI models via API - use letz.ai web interface
- API Key Required: Paid subscription required for API access
Quick Reference: API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/images | GET | List user's images |
/images | POST | Create image (prompt, baseModel, mode, width, height) |
/images/{id} | GET | Get image status & URLs (poll every 3s) |
/images/{id}/interruption | PUT | Stop image generation |
/images/{id}/privacy | PUT | Change image privacy |
/videos | GET | List user's videos |
/videos | POST | Create video (prompt, imageUrl, settings) |
/videos/{id} | GET | Get video status & URLs (poll every 2-3s) |
/videos/{id}/interruption | PUT | Stop video generation |
/videos/{id}/privacy | PUT | Change video privacy |
/image-edits | GET | List user's edits |
/image-edits | POST | Edit image (mode, prompt, imageUrl/inputImageUrls, settings) |
/image-edits/{id} | GET | Get edit status & URLs (poll every 3s) |
/upscales | POST | Upscale image (imageUrl/imageUrls, strength, mode, size) |
/upscales/{id} | GET | Get upscale status & URLs (poll every 3s) |
/models | GET | List trained AI models (filter by class: person/object/style) |
/models/{id} | GET | Get specific model details |
Key Response Fields
- Images/Upscales:
imageVersions.original,imageVersions["1920x1920"],imageVersions["640x640"] - Edits:
generatedImageCompletion.imageVersions.original - Videos:
videoPathsobject,videoVersionsarray - Status values:
new,in progress/generating,ready,failed
Additional Resources
- API Documentation: api.letz.ai/doc
- Developer Docs: letz.ai/docs/api
- Detailed API Reference: api_reference.md
- Code Examples: examples/
LetzAI API Reference
Complete API documentation for the LetzAI image and video generation platform.
Base Configuration
| Property | Value |
|---|---|
| Base URL | https://api.letz.ai |
| Authentication | Bearer Token |
| Content-Type | application/json |
| Swagger Docs | api.letz.ai/doc |
Authentication
All API requests require authentication via Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEYGet your API key at letz.ai/subscription.
---
Image Generation
Create Image
Endpoint: POST /images
Creates a new AI-generated image based on the provided prompt and settings.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Text description of the desired image. Can include @modelname to use trained models. |
baseModel | string | No | AI model to use (see Available Base Models) |
mode | string | No | Resolution/quality mode (model-dependent) |
width | integer | No | Image width in pixels (520-2160) |
height | integer | No | Image height in pixels (520-2160) |
negativePrompt | string | No | Elements to exclude from the image |
seed | integer | No | Seed for reproducible results |
aspectRatio | string | No | Aspect ratio (e.g., "16:9", "1:1", "9:16") |
Available Base Models
| Model Name | API Value | Available Modes | Default Mode |
|---|---|---|---|
| Nano Banana Pro | gemini-3-pro-image-preview | default, 2k, 4k | default |
| Flux2 Max | flux2-max | 1k, hd | 1k |
| SeeDream 4.5 | seedream-4-5-251128 | 2k, 4k | 2k |
Example Request
{
"prompt": "A majestic mountain landscape at sunset with golden light",
"baseModel": "gemini-3-pro-image-preview",
"mode": "2k",
"width": 1920,
"height": 1080,
"negativePrompt": "blurry, low quality"
}Response
{
"id": "img_abc123xyz",
"status": "new",
"createdAt": "2025-01-27T10:00:00Z"
}Get Image Status
Endpoint: GET /images/{id}
Retrieves the status and result of an image generation job.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The image generation job ID |
Response (Pending)
{
"id": "img_abc123xyz",
"status": "in progress",
"progress": 45,
"createdAt": "2025-01-27T10:00:00Z"
}Response (Complete)
{
"id": "img_abc123xyz",
"status": "ready",
"prompt": "A majestic mountain landscape at sunset",
"baseModel": "gemini-3-pro-image-preview",
"mode": "2k",
"width": 1920,
"height": 1080,
"imageVersions": {
"original": "https://cdn.letz.ai/images/img_abc123xyz/original.png",
"1920x1920": "https://cdn.letz.ai/images/img_abc123xyz/1920x1920.png",
"640x640": "https://cdn.letz.ai/images/img_abc123xyz/640x640.png"
},
"creditsUsed": 160,
"createdAt": "2025-01-27T10:00:00Z",
"completedAt": "2025-01-27T10:00:25Z"
}List Images
Endpoint: GET /images
Retrieves a list of user's generated images.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 10 | Results per page |
Interrupt Image Generation
Endpoint: PUT /images/{id}/interruption
Stops an in-progress image generation job.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The image generation job ID |
Change Image Privacy
Endpoint: PUT /images/{id}/privacy
Changes the privacy setting of a generated image.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The image ID |
---
Video Generation
Create Video
Endpoint: POST /videos
Creates a new AI-generated video from a source image.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Text description of the desired video motion |
imageUrl | string | Conditional | URL of source image (required if no imageCompletionId) |
originalImageCompletionId | string | Conditional | ID of previously generated image |
settings.mode | string | No | Video model (default, veo31, kling26, wan25) |
settings.duration | integer | No | Video duration in seconds |
settings.fps | integer | No | Frames per second |
Available Video Models
| Model Name | API Value | Duration Range | Notes |
|---|---|---|---|
| Default | default | 2-6 sec | Most cost-effective |
| VEO 3.1 | veo31 | 8 sec | Highest quality |
| Kling 2.6 | kling26 | 5-10 sec | Balanced |
| Wan 2.5 | wan25 | 5-10 sec | Good motion |
Example Request
{
"prompt": "The camera slowly pans across the mountain as clouds drift by",
"originalImageCompletionId": "img_abc123xyz",
"settings": {
"mode": "kling26",
"duration": 5
}
}Response
{
"id": "vid_def456uvw",
"status": "new",
"createdAt": "2025-01-27T10:05:00Z"
}Get Video Status
Endpoint: GET /videos/{id}
Retrieves the status and result of a video generation job.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The video generation job ID |
Response (Complete)
{
"id": "vid_def456uvw",
"status": "ready",
"prompt": "The camera slowly pans across the mountain",
"settings": {
"mode": "kling26",
"duration": 5
},
"videoPaths": {
"mp4": "https://cdn.letz.ai/videos/vid_def456uvw/video.mp4",
"webm": "https://cdn.letz.ai/videos/vid_def456uvw/video.webm"
},
"thumbnailUrl": "https://cdn.letz.ai/videos/vid_def456uvw/thumbnail.jpg",
"creditsUsed": 750,
"createdAt": "2025-01-27T10:05:00Z",
"completedAt": "2025-01-27T10:06:30Z"
}List Videos
Endpoint: GET /videos
Retrieves a list of user's generated videos.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 10 | Results per page |
Interrupt Video Generation
Endpoint: PUT /videos/{id}/interruption
Stops an in-progress video generation job.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The video generation job ID |
Change Video Privacy
Endpoint: PUT /videos/{id}/privacy
Changes the privacy setting of a generated video.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The video ID |
---
Image Editing (Context Editing)
Create Image Edit
Endpoint: POST /image-edits
Edits an existing image using AI-powered modifications. The primary mode is "context" editing.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | Edit mode: "context" (AI editing) or "skin" (skin fix) |
prompt | string | Yes | Edit instruction (e.g., "change background to sunset") |
imageUrl | string | Conditional | URL of source image |
inputImageUrls | array | Conditional | Array of source image URLs (max 9) |
originalImageCompletionId | string | Conditional | ID of previously generated LetzAI image |
settings | object | No | Configuration options (see below) |
baseModel | string | No | Alternative to settings.model |
organizationId | string | No | Optional org ID for billing |
webhookUrl | string | No | Optional callback URL |
Settings Object
| Parameter | Type | Description |
|---|---|---|
resolution | string | "2k" (HD) or "4k" (Ultra HD) |
aspect_ratio | string | "1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21" |
model | string | "gemini-3-pro-image-preview", "flux2-max", "seedream-4-5-251128" |
Edit Modes
| Mode | Description |
|---|---|
context | AI-powered contextual editing (primary mode) |
skin | Skin fix/enhancement |
Note: Inpainting (mode: "in") and Outpainting (mode: "out") are deprecated - use Context Editing instead.
Example Request - Single Image
{
"mode": "context",
"prompt": "Change the background to a tropical beach with palm trees",
"imageUrl": "https://example.com/my-photo.jpg",
"settings": {
"resolution": "2k",
"aspect_ratio": "16:9",
"model": "gemini-3-pro-image-preview"
}
}Example Request - Multi-Reference Editing
{
"mode": "context",
"prompt": "Combine elements from these images into a cohesive scene",
"inputImageUrls": [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg"
],
"settings": {
"resolution": "4k",
"model": "gemini-3-pro-image-preview"
}
}Response (Initial)
{
"id": "edit_ghi789rst",
"status": "new",
"createdAt": "2025-01-27T10:10:00Z"
}Get Image Edit Status
Endpoint: GET /image-edits/{id}
Retrieves the status and result of an image edit job.
Response (Complete)
{
"id": "edit_ghi789rst",
"status": "ready",
"mode": "context",
"prompt": "Change the background to a tropical beach",
"originalImageCompletion": {
"imageVersions": {
"original": "https://images.letz.ai/..."
}
},
"generatedImageCompletion": {
"imageVersions": {
"original": "https://images.letz.ai/edited/original.png",
"1920x1920": "https://images.letz.ai/edited/1920x1920.png",
"640x640": "https://images.letz.ai/edited/640x640.png"
}
},
"creditsUsed": 160,
"createdAt": "2025-01-27T10:10:00Z",
"completedAt": "2025-01-27T10:10:30Z"
}Important: Access the edited image via generatedImageCompletion.imageVersions.original
List Image Edits
Endpoint: GET /image-edits
Retrieves a list of user's image edits.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 10 | Results per page |
---
Image Upscaling
Create Upscale
Endpoint: POST /upscales
Upscales an image to higher resolution.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
imageUrl | string | Conditional | URL of single source image |
imageUrls | array | Conditional | Array of image URLs for batch upscaling |
imageCompletionId | string | Conditional | ID of previously generated image |
strength | integer | No | Upscale factor (1-3, default: 2) |
mode | string | No | Upscale mode |
size | integer | No | Target size |
Example Request - Single Image
{
"imageCompletionId": "img_abc123xyz",
"strength": 2
}Example Request - Batch Upscaling
{
"imageUrls": [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg"
],
"strength": 2
}Response
{
"id": "ups_jkl012mno",
"status": "new",
"createdAt": "2025-01-27T10:15:00Z"
}Get Upscale Status
Endpoint: GET /upscales/{id}
Retrieves the status and result of an upscale job.
---
Custom AI Models (Trained Models)
LetzAI users can train custom AI models on persons, objects, or styles via the web interface. These trained models can then be used in prompts via the @modelname syntax.
List Trained Models
Endpoint: GET /models
Retrieves a list of user's trained AI models.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number for pagination |
limit | integer | 10 | Number of results per page |
sortBy | string | - | Sort field: "createdAt" or "usages" |
sortOrder | string | - | Sort direction: "ASC" or "DESC" |
class | string | - | Filter by model class: "person", "object", "style" |
Model Classes
| Class | Description |
|---|---|
person | Trained on photos of a specific person |
object | Trained on product/object images |
style | Trained on artistic style examples |
Example Request
GET /models?class=person&limit=5&sortBy=usages&sortOrder=DESCResponse
{
"data": [
{
"id": "model_abc123",
"name": "john_doe",
"class": "person",
"createdAt": "2025-01-15T08:00:00Z",
"usages": 42,
"thumbnail": "https://cdn.letz.ai/models/model_abc123/thumb.jpg"
},
{
"id": "model_def456",
"name": "vintage_style",
"class": "style",
"createdAt": "2025-01-10T12:00:00Z",
"usages": 28,
"thumbnail": "https://cdn.letz.ai/models/model_def456/thumb.jpg"
}
],
"page": 1,
"limit": 5,
"total": 12
}Get Model Details
Endpoint: GET /models/{id}
Retrieves detailed information about a specific trained model.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The trained model ID |
Response
{
"id": "model_abc123",
"name": "john_doe",
"class": "person",
"createdAt": "2025-01-15T08:00:00Z",
"usages": 42,
"thumbnail": "https://cdn.letz.ai/models/model_abc123/thumb.jpg",
"trainingImages": 20,
"status": "ready"
}Using Trained Models in Prompts
Tag models in prompts using @modelname syntax:
// Generate image with a person model
{
"prompt": "@john_doe on the beach at sunset",
"baseModel": "gemini-3-pro-image-preview",
"mode": "2k"
}
// Generate image with an object model
{
"prompt": "A product photo featuring @my_product in studio lighting",
"baseModel": "gemini-3-pro-image-preview",
"mode": "2k"
}
// Generate image with a style model
{
"prompt": "Portrait of a woman in @vintage_style aesthetic",
"baseModel": "gemini-3-pro-image-preview",
"mode": "2k"
}Note: Model training is done via the LetzAI web interface (letz.ai), not via API.
---
Status Values
All generation jobs use the following status values:
| Status | Description |
|---|---|
new | Job has been created and queued |
in progress | Job is currently being processed |
generating | Alternative status for processing |
ready | Job completed successfully |
failed | Job failed - check error field |
---
Error Responses
Error Format
{
"error": "Description of the error",
"code": "ERROR_CODE",
"details": {}
}HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing API key |
| 402 | Payment Required - Insufficient credits |
| 404 | Not Found - Resource doesn't exist |
| 429 | Too Many Requests - Rate limited |
| 500 | Internal Server Error |
Common Error Codes
| Code | Description |
|---|---|
INVALID_API_KEY | The provided API key is invalid |
INSUFFICIENT_CREDITS | Account doesn't have enough credits |
INVALID_MODEL | The specified model doesn't exist |
INVALID_PARAMETERS | Request parameters are invalid |
CONTENT_POLICY_VIOLATION | Prompt violates content policy |
GENERATION_FAILED | The generation process failed |
---
Webhooks
LetzAI supports webhooks for receiving notifications when jobs complete. Include a webhookUrl parameter when creating jobs:
{
"prompt": "A beautiful landscape",
"baseModel": "gemini-3-pro-image-preview",
"webhookUrl": "https://your-server.com/api/letzai/callback"
}The webhook will receive a POST request when the job completes or fails.
Note: Refer to api.letz.ai/doc for complete webhook payload documentation.
/**
* LetzAI Image Generation Examples
*
* This file demonstrates how to generate images using the LetzAI API
* with various models and configurations.
*/
const API_BASE_URL = 'https://api.letz.ai';
// Replace with your actual API key
const API_KEY = process.env.LETZAI_API_KEY || 'YOUR_API_KEY';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
};
/**
* Sleep utility for polling
*/
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
* Poll for image completion
* @param {string} imageId - The image generation job ID
* @param {number} intervalMs - Polling interval in milliseconds (default: 3000)
* @param {number} maxAttempts - Maximum polling attempts (default: 60)
* @returns {Promise<Object>} - The completed image object
*/
async function pollImageStatus(imageId, intervalMs = 3000, maxAttempts = 60) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(`${API_BASE_URL}/images/${imageId}`, { headers });
const data = await response.json();
console.log(`Attempt ${attempt + 1}: Status = ${data.status}`);
if (data.status === 'ready') {
return data;
}
if (data.status === 'failed') {
throw new Error(`Image generation failed: ${data.error || 'Unknown error'}`);
}
await sleep(intervalMs);
}
throw new Error('Image generation timed out');
}
/**
* Example 1: Basic Image Generation with Nano Banana Pro
*
* Uses the recommended model for high-quality images
*/
async function generateBasicImage() {
console.log('=== Basic Image Generation ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: 'A beautiful sunset over a calm ocean with vibrant orange and purple colors',
baseModel: 'gemini-3-pro-image-preview',
mode: '2k'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Image generation started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
console.log(`Credits used: ${result.creditsUsed}`);
return result;
}
/**
* Example 2: High-Resolution Image with Custom Dimensions
*
* Generates a 4K image with specific dimensions
*/
async function generateHighResImage() {
console.log('=== High-Resolution Image Generation ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: 'A majestic eagle soaring through snow-capped mountains, dramatic lighting, photorealistic',
baseModel: 'gemini-3-pro-image-preview',
mode: '4k',
width: 2160,
height: 1440,
negativePrompt: 'blurry, low quality, distorted'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`High-res generation started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
console.log(`Dimensions: ${result.width}x${result.height}`);
return result;
}
/**
* Example 3: Image Generation with Flux2 Max
*
* Uses the Flux2 Max model for creative styles
*/
async function generateFlux2Image() {
console.log('=== Flux2 Max Image Generation ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: 'Cyberpunk city street at night, neon lights reflecting on wet pavement, futuristic',
baseModel: 'flux2-max',
mode: 'hd',
aspectRatio: '16:9'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Flux2 generation started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
return result;
}
/**
* Example 4: Image Generation with SeeDream
*
* Uses the SeeDream 4.5 model
*/
async function generateSeeDreamImage() {
console.log('=== SeeDream Image Generation ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: 'A serene Japanese garden with cherry blossoms, koi pond, and wooden bridge',
baseModel: 'seedream-4-5-251128',
mode: '2k'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`SeeDream generation started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
return result;
}
/**
* Example 5: Reproducible Generation with Seed
*
* Uses a seed for reproducible results
*/
async function generateWithSeed() {
console.log('=== Reproducible Generation with Seed ===\n');
const seed = 12345;
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: 'A fantasy castle on a floating island surrounded by clouds',
baseModel: 'gemini-3-pro-image-preview',
mode: '2k',
seed: seed
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Generation with seed ${seed} started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
console.log(`Use the same seed (${seed}) to reproduce this exact image`);
return result;
}
/**
* Example 6: Batch Generation with Different Models
*
* Generates multiple images with different models in parallel
*/
async function generateBatch() {
console.log('=== Batch Generation ===\n');
const prompts = [
{
prompt: 'Abstract art with flowing colors',
baseModel: 'gemini-3-pro-image-preview',
mode: 'default'
},
{
prompt: 'Abstract art with flowing colors',
baseModel: 'flux2-max',
mode: '1k'
},
{
prompt: 'Abstract art with flowing colors',
baseModel: 'seedream-4-5-251128',
mode: '2k'
}
];
// Start all generations
const startPromises = prompts.map(async (config) => {
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify(config)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const data = await response.json();
console.log(`Started ${config.baseModel}: ${data.id}`);
return { ...data, model: config.baseModel };
});
const jobs = await Promise.all(startPromises);
// Poll all in parallel
const results = await Promise.all(
jobs.map(job => pollImageStatus(job.id))
);
console.log('\n=== All Images Ready ===');
results.forEach((result, i) => {
console.log(`\n${jobs[i].model}:`);
console.log(` URL: ${result.imageVersions.original}`);
console.log(` Credits: ${result.creditsUsed}`);
});
return results;
}
/**
* Example 7: Error Handling
*
* Demonstrates proper error handling
*/
async function generateWithErrorHandling() {
console.log('=== Image Generation with Error Handling ===\n');
try {
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: 'A beautiful landscape',
baseModel: 'gemini-3-pro-image-preview',
mode: '2k'
})
});
// Handle HTTP errors
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
throw new Error('Invalid API key. Please check your credentials.');
case 402:
throw new Error('Insufficient credits. Please top up at letz.ai/subscription');
case 400:
throw new Error(`Invalid parameters: ${error.error}`);
case 429:
throw new Error('Rate limited. Please wait before making more requests.');
default:
throw new Error(`API Error (${response.status}): ${error.error}`);
}
}
const { id } = await response.json();
console.log(`Generation started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
return result;
} catch (error) {
console.error(`Error: ${error.message}`);
// Implement retry logic for transient errors
if (error.message.includes('Rate limited')) {
console.log('Retrying after delay...');
await sleep(5000);
// Could retry here
}
throw error;
}
}
// =============================================================================
// Context Editing Examples
// =============================================================================
/**
* Poll for image edit completion
* @param {string} editId - The image edit job ID
* @param {number} intervalMs - Polling interval in milliseconds (default: 3000)
* @param {number} maxAttempts - Maximum polling attempts (default: 60)
* @returns {Promise<Object>} - The completed edit object
*/
async function pollEditStatus(editId, intervalMs = 3000, maxAttempts = 60) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(`${API_BASE_URL}/image-edits/${editId}`, { headers });
const data = await response.json();
console.log(`Attempt ${attempt + 1}: Status = ${data.status}`);
if (data.status === 'ready') {
return data;
}
if (data.status === 'failed') {
throw new Error(`Image edit failed: ${data.error || 'Unknown error'}`);
}
await sleep(intervalMs);
}
throw new Error('Image edit timed out');
}
/**
* Example: Context Editing - Single Image
*
* Edits an existing image using AI-powered context editing
*/
async function contextEditSingleImage(imageUrl, editPrompt) {
console.log('=== Context Edit - Single Image ===\n');
const response = await fetch(`${API_BASE_URL}/image-edits`, {
method: 'POST',
headers,
body: JSON.stringify({
mode: 'context',
prompt: editPrompt,
imageUrl: imageUrl,
settings: {
resolution: '2k',
aspect_ratio: '16:9',
model: 'gemini-3-pro-image-preview'
}
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Edit started. ID: ${id}`);
const result = await pollEditStatus(id);
console.log(`\nEdit complete!`);
console.log(`Edited image: ${result.generatedImageCompletion.imageVersions.original}`);
return result;
}
/**
* Example: Context Editing - Multi-Reference
*
* Uses multiple reference images for editing
*/
async function contextEditMultiReference(imageUrls, editPrompt) {
console.log('=== Context Edit - Multi-Reference ===\n');
if (imageUrls.length > 9) {
throw new Error('Maximum 9 reference images allowed');
}
const response = await fetch(`${API_BASE_URL}/image-edits`, {
method: 'POST',
headers,
body: JSON.stringify({
mode: 'context',
prompt: editPrompt,
inputImageUrls: imageUrls,
settings: {
resolution: '4k',
model: 'gemini-3-pro-image-preview'
}
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Multi-reference edit started. ID: ${id}`);
const result = await pollEditStatus(id);
console.log(`\nEdit complete!`);
console.log(`Edited image: ${result.generatedImageCompletion.imageVersions.original}`);
return result;
}
/**
* Example: Edit Previously Generated LetzAI Image
*
* Uses originalImageCompletionId to edit an existing LetzAI image
*/
async function editLetzAIImage(imageCompletionId, editPrompt) {
console.log('=== Edit LetzAI Image ===\n');
const response = await fetch(`${API_BASE_URL}/image-edits`, {
method: 'POST',
headers,
body: JSON.stringify({
mode: 'context',
prompt: editPrompt,
originalImageCompletionId: imageCompletionId,
settings: {
resolution: '2k',
model: 'gemini-3-pro-image-preview'
}
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Edit of ${imageCompletionId} started. ID: ${id}`);
const result = await pollEditStatus(id);
console.log(`\nEdit complete!`);
console.log(`Original: ${result.originalImageCompletion?.imageVersions?.original || 'N/A'}`);
console.log(`Edited: ${result.generatedImageCompletion.imageVersions.original}`);
return result;
}
// =============================================================================
// Custom Trained Models Examples
// =============================================================================
/**
* Example 8: List Available Trained Models
*
* Retrieves user's custom trained models (persons, objects, styles)
*/
async function listTrainedModels(modelClass = null) {
console.log('=== List Trained Models ===\n');
let url = `${API_BASE_URL}/models?limit=10&sortBy=usages&sortOrder=DESC`;
if (modelClass) {
url += `&class=${modelClass}`;
}
const response = await fetch(url, { headers });
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const data = await response.json();
console.log(`Found ${data.data?.length || 0} models:`);
(data.data || []).forEach(model => {
console.log(` - @${model.name} (${model.class}) - ${model.usages} uses`);
});
return data;
}
/**
* Example 9: Generate Image with Custom Person Model
*
* Uses a trained person model via @modelname syntax
*/
async function generateWithPersonModel(modelName) {
console.log('=== Generate with Person Model ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: `@${modelName} standing on a beach at sunset, professional photography`,
baseModel: 'gemini-3-pro-image-preview',
mode: '2k'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Generation with @${modelName} started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
return result;
}
/**
* Example 10: Generate Image with Custom Style Model
*
* Uses a trained style model for artistic effects
*/
async function generateWithStyleModel(styleName) {
console.log('=== Generate with Style Model ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: `A portrait of a woman in @${styleName} aesthetic, dramatic lighting`,
baseModel: 'gemini-3-pro-image-preview',
mode: '2k'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Generation with @${styleName} style started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
return result;
}
/**
* Example 11: Generate Product Photo with Object Model
*
* Uses a trained object/product model
*/
async function generateWithObjectModel(objectName) {
console.log('=== Generate with Object Model ===\n');
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: `Professional product photo featuring @${objectName} on a white background, studio lighting`,
baseModel: 'gemini-3-pro-image-preview',
mode: '2k'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Generation with @${objectName} started. ID: ${id}`);
const result = await pollImageStatus(id);
console.log(`\nImage ready!`);
console.log(`URL: ${result.imageVersions.original}`);
return result;
}
/**
* Example 12: Complete Workflow with Custom Model
*
* Lists models, selects one, and generates an image
*/
async function completeCustomModelWorkflow() {
console.log('=== Complete Custom Model Workflow ===\n');
// Step 1: List available person models
console.log('Step 1: Fetching available person models...');
const modelsResponse = await fetch(
`${API_BASE_URL}/models?class=person&limit=5&sortBy=usages&sortOrder=DESC`,
{ headers }
);
if (!modelsResponse.ok) {
throw new Error('Failed to fetch models');
}
const modelsData = await modelsResponse.json();
const models = modelsData.data || [];
if (models.length === 0) {
console.log('No person models found. Train models at letz.ai first.');
return null;
}
console.log(`Found ${models.length} person models:`);
models.forEach(m => console.log(` - @${m.name}`));
// Step 2: Use the most-used model
const selectedModel = models[0];
console.log(`\nStep 2: Using @${selectedModel.name} for generation...`);
const response = await fetch(`${API_BASE_URL}/images`, {
method: 'POST',
headers,
body: JSON.stringify({
prompt: `@${selectedModel.name} in a modern office, professional headshot`,
baseModel: 'gemini-3-pro-image-preview',
mode: '2k'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.error}`);
}
const { id } = await response.json();
console.log(`Generation started. ID: ${id}`);
// Step 3: Poll for result
console.log('\nStep 3: Waiting for generation...');
const result = await pollImageStatus(id);
console.log('\n=== Workflow Complete ===');
console.log(`Model used: @${selectedModel.name}`);
console.log(`Image URL: ${result.imageVersions.original}`);
return result;
}
// Main execution
async function main() {
try {
// Run examples
await generateBasicImage();
console.log('\n---\n');
// Uncomment to run other examples:
// await generateHighResImage();
// await generateFlux2Image();
// await generateSeeDreamImage();
// await generateWithSeed();
// await generateBatch();
// await generateWithErrorHandling();
// Custom model examples (requires trained models):
// await listTrainedModels('person');
// await generateWithPersonModel('john_doe');
// await generateWithStyleModel('vintage_style');
// await generateWithObjectModel('my_product');
// await completeCustomModelWorkflow();
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
// Run if executed directly
main();
// Export functions for use as module
module.exports = {
// Base model examples
generateBasicImage,
generateHighResImage,
generateFlux2Image,
generateSeeDreamImage,
generateWithSeed,
generateBatch,
generateWithErrorHandling,
// Context editing examples
contextEditSingleImage,
contextEditMultiReference,
editLetzAIImage,
// Custom model examples
listTrainedModels,
generateWithPersonModel,
generateWithStyleModel,
generateWithObjectModel,
completeCustomModelWorkflow,
// Utilities
pollImageStatus,
pollEditStatus
};
LetzAI Polling Pattern Guide
LetzAI uses asynchronous generation for all image and video operations. This guide explains how to properly implement polling to check job status and retrieve results.
Why Polling?
AI image and video generation takes time (seconds to minutes). Instead of keeping connections open, LetzAI uses an async pattern:
1. Submit Job → Receive job ID immediately 2. Poll Status → Check periodically until complete 3. Get Result → Fetch URLs when ready
Status Flow
┌─────┐ ┌─────────────┐ ┌───────┐
│ new │ ──> │ in progress │ ──> │ ready │
└─────┘ └─────────────┘ └───────┘
│
v
┌────────┐
│ failed │
└────────┘Status Values
| Status | Description | Action |
|---|---|---|
new | Job queued | Continue polling |
in progress | Processing | Continue polling |
generating | Alternative for processing | Continue polling |
ready | Complete! | Fetch result URLs |
failed | Error occurred | Handle error, stop polling |
Recommended Polling Intervals
| Operation | Interval | Typical Wait Time |
|---|---|---|
| Images | 3 seconds | 10-30 seconds |
| Videos | 2-3 seconds | 30-120 seconds |
| Image Edits | 3 seconds | 15-45 seconds |
| Upscales | 3 seconds | 10-30 seconds |
Implementation Patterns
JavaScript/TypeScript
async function pollUntilReady(endpoint, jobId, intervalMs = 3000, maxAttempts = 60) {
const url = `https://api.letz.ai/${endpoint}/${jobId}`;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
switch (data.status) {
case 'ready':
return data;
case 'failed':
throw new Error(data.error || 'Generation failed');
case 'new':
case 'in progress':
case 'generating':
// Continue polling
await new Promise(r => setTimeout(r, intervalMs));
break;
default:
console.warn(`Unknown status: ${data.status}`);
await new Promise(r => setTimeout(r, intervalMs));
}
}
throw new Error(`Timeout: Job did not complete in ${maxAttempts} attempts`);
}
// Usage
const imageResult = await pollUntilReady('images', imageId, 3000);
const videoResult = await pollUntilReady('videos', videoId, 2500);Python
import time
import requests
def poll_until_ready(endpoint: str, job_id: str, interval: float = 3.0, max_attempts: int = 60):
"""
Poll LetzAI API until job completes.
Args:
endpoint: API endpoint ('images', 'videos', 'image-edits', 'upscales')
job_id: The job ID to poll
interval: Seconds between polls
max_attempts: Maximum polling attempts before timeout
Returns:
dict: The completed job data
Raises:
TimeoutError: If job doesn't complete
RuntimeError: If job fails
"""
url = f"https://api.letz.ai/{endpoint}/{job_id}"
headers = {"Authorization": f"Bearer {API_KEY}"}
for attempt in range(max_attempts):
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
status = data.get("status", "")
if status == "ready":
return data
elif status == "failed":
raise RuntimeError(data.get("error", "Generation failed"))
elif status in ("new", "in progress", "generating"):
time.sleep(interval)
else:
print(f"Warning: Unknown status '{status}'")
time.sleep(interval)
raise TimeoutError(f"Job {job_id} timed out after {max_attempts} attempts")
# Usage
image_result = poll_until_ready("images", image_id, interval=3.0)
video_result = poll_until_ready("videos", video_id, interval=2.5)cURL / Shell Script
#!/bin/bash
API_KEY="your_api_key"
JOB_ID="$1"
ENDPOINT="${2:-images}" # Default to images
INTERVAL="${3:-3}" # Default 3 seconds
MAX_ATTEMPTS="${4:-60}" # Default 60 attempts
poll_job() {
local attempt=0
while [ $attempt -lt $MAX_ATTEMPTS ]; do
response=$(curl -s -H "Authorization: Bearer $API_KEY" \
"https://api.letz.ai/${ENDPOINT}/${JOB_ID}")
status=$(echo "$response" | jq -r '.status')
case "$status" in
"ready")
echo "Job complete!"
echo "$response" | jq
return 0
;;
"failed")
echo "Job failed!"
echo "$response" | jq
return 1
;;
*)
echo "Attempt $((attempt + 1)): Status = $status"
sleep $INTERVAL
;;
esac
attempt=$((attempt + 1))
done
echo "Timeout: Job did not complete"
return 1
}
poll_jobAdvanced Patterns
Exponential Backoff
For long-running jobs (especially videos), consider exponential backoff:
async function pollWithBackoff(endpoint, jobId, initialInterval = 2000, maxInterval = 30000) {
let interval = initialInterval;
while (true) {
const response = await fetch(`https://api.letz.ai/${endpoint}/${jobId}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const data = await response.json();
if (data.status === 'ready') return data;
if (data.status === 'failed') throw new Error(data.error);
await new Promise(r => setTimeout(r, interval));
// Increase interval, up to max
interval = Math.min(interval * 1.5, maxInterval);
}
}Progress Tracking
Some responses include progress information:
async function pollWithProgress(endpoint, jobId, onProgress) {
while (true) {
const response = await fetch(`https://api.letz.ai/${endpoint}/${jobId}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const data = await response.json();
// Report progress if available
if (data.progress && onProgress) {
onProgress(data.progress);
}
if (data.status === 'ready') return data;
if (data.status === 'failed') throw new Error(data.error);
await new Promise(r => setTimeout(r, 3000));
}
}
// Usage with progress callback
const result = await pollWithProgress('images', imageId, (progress) => {
console.log(`Progress: ${progress}%`);
});Parallel Polling
Poll multiple jobs simultaneously:
async function pollMultiple(jobs) {
// jobs = [{ endpoint: 'images', id: 'abc' }, { endpoint: 'videos', id: 'xyz' }]
return Promise.all(
jobs.map(job => pollUntilReady(job.endpoint, job.id))
);
}
// Usage
const [image1, image2, video1] = await pollMultiple([
{ endpoint: 'images', id: imageId1 },
{ endpoint: 'images', id: imageId2 },
{ endpoint: 'videos', id: videoId1 }
]);Error Handling
Common Errors During Polling
| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Invalid/expired API key | Check API key |
| 404 Not Found | Invalid job ID | Verify ID from creation response |
| 429 Rate Limited | Too many requests | Increase polling interval |
| 500 Server Error | API issue | Retry with backoff |
Robust Error Handling
async function robustPoll(endpoint, jobId) {
let retries = 0;
const maxRetries = 3;
while (true) {
try {
const response = await fetch(`https://api.letz.ai/${endpoint}/${jobId}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
if (response.status === 429) {
// Rate limited - wait longer
const retryAfter = response.headers.get('Retry-After') || 30;
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
if (response.status === 500 && retries < maxRetries) {
// Server error - retry with backoff
retries++;
await new Promise(r => setTimeout(r, Math.pow(2, retries) * 1000));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.status === 'ready') return data;
if (data.status === 'failed') throw new Error(data.error);
// Reset retries on successful poll
retries = 0;
await new Promise(r => setTimeout(r, 3000));
} catch (error) {
if (error.name === 'AbortError' || retries >= maxRetries) {
throw error;
}
retries++;
await new Promise(r => setTimeout(r, Math.pow(2, retries) * 1000));
}
}
}Best Practices
1. Don't poll too frequently - Respect the recommended intervals (3s for images, 2-3s for videos)
2. Set reasonable timeouts - Images typically complete in 10-30s, videos in 30-120s
3. Handle all status values - Always have a case for unknown statuses
4. Log progress - Helpful for debugging and user feedback
5. Implement backoff - For production systems, use exponential backoff
6. Cancel capability - Allow users to cancel long-running polls
7. Store job IDs - Save IDs to resume polling after page refresh or app restart
Webhook Alternative
For production systems, consider using webhooks instead of polling:
// When creating the job, include webhook URL
const response = await fetch('https://api.letz.ai/images', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: 'A beautiful landscape',
webhookUrl: 'https://your-server.com/api/letzai/callback'
})
});
// Your server receives a POST when complete:
// POST /api/letzai/callback
// Body: { event: 'image.completed', data: { id, status, imageVersions, ... } }See the API Reference for webhook configuration details.
"""
LetzAI Video Generation Examples
This module demonstrates how to generate videos using the LetzAI API
with various models and configurations.
"""
import os
import time
import requests
from typing import Optional, Dict, Any
API_BASE_URL = "https://api.letz.ai"
API_KEY = os.environ.get("LETZAI_API_KEY", "YOUR_API_KEY")
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}"
}
def poll_status(
endpoint: str,
job_id: str,
interval_seconds: float = 3.0,
max_attempts: int = 120
) -> Dict[str, Any]:
"""
Poll for job completion.
Args:
endpoint: API endpoint (e.g., 'images', 'videos')
job_id: The job ID to poll
interval_seconds: Time between polls (default: 3s for images, 2-3s for videos)
max_attempts: Maximum number of polling attempts
Returns:
The completed job data
Raises:
TimeoutError: If job doesn't complete within max_attempts
RuntimeError: If job fails
"""
url = f"{API_BASE_URL}/{endpoint}/{job_id}"
for attempt in range(max_attempts):
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
data = response.json()
status = data.get("status", "")
print(f"Attempt {attempt + 1}: Status = {status}")
if status == "ready":
return data
if status == "failed":
error_msg = data.get("error", "Unknown error")
raise RuntimeError(f"Job failed: {error_msg}")
time.sleep(interval_seconds)
raise TimeoutError(f"Job {job_id} did not complete within {max_attempts} attempts")
def poll_image_status(image_id: str) -> Dict[str, Any]:
"""Poll for image completion (3s interval)."""
return poll_status("images", image_id, interval_seconds=3.0)
def poll_video_status(video_id: str) -> Dict[str, Any]:
"""Poll for video completion (2.5s interval)."""
return poll_status("videos", video_id, interval_seconds=2.5)
# =============================================================================
# Image Generation (needed for video source)
# =============================================================================
def generate_source_image(prompt: str) -> Dict[str, Any]:
"""
Generate a source image for video creation.
Args:
prompt: Text description of the image
Returns:
The completed image data including ID and URLs
"""
print("=== Generating Source Image ===\n")
response = requests.post(
f"{API_BASE_URL}/images",
headers=HEADERS,
json={
"prompt": prompt,
"baseModel": "gemini-3-pro-image-preview",
"mode": "2k"
}
)
response.raise_for_status()
data = response.json()
image_id = data["id"]
print(f"Image generation started. ID: {image_id}")
result = poll_image_status(image_id)
print(f"\nImage ready!")
print(f"URL: {result['imageVersions']['original']}")
return result
# =============================================================================
# Video Generation Examples
# =============================================================================
def generate_video_from_url(
prompt: str,
image_url: str,
mode: str = "default",
duration: int = 5
) -> Dict[str, Any]:
"""
Example 1: Generate video from an image URL.
Args:
prompt: Text description of the video motion
image_url: URL of the source image
mode: Video model ('default', 'veo31', 'kling26', 'wan25')
duration: Video duration in seconds
Returns:
The completed video data
"""
print(f"=== Video Generation from URL ({mode}) ===\n")
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"imageUrl": image_url,
"settings": {
"mode": mode,
"duration": duration
}
}
)
response.raise_for_status()
data = response.json()
video_id = data["id"]
print(f"Video generation started. ID: {video_id}")
result = poll_video_status(video_id)
print(f"\nVideo ready!")
print(f"MP4 URL: {result['videoPaths'].get('mp4', 'N/A')}")
print(f"Credits used: {result.get('creditsUsed', 'N/A')}")
return result
def generate_video_from_image_id(
prompt: str,
image_completion_id: str,
mode: str = "kling26",
duration: int = 5
) -> Dict[str, Any]:
"""
Example 2: Generate video from a previously generated image ID.
This is more efficient if you've already generated an image with LetzAI.
Args:
prompt: Text description of the video motion
image_completion_id: ID from previous image generation
mode: Video model
duration: Video duration in seconds
Returns:
The completed video data
"""
print(f"=== Video Generation from Image ID ({mode}) ===\n")
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"originalImageCompletionId": image_completion_id,
"settings": {
"mode": mode,
"duration": duration
}
}
)
response.raise_for_status()
data = response.json()
video_id = data["id"]
print(f"Video generation started. ID: {video_id}")
result = poll_video_status(video_id)
print(f"\nVideo ready!")
print(f"MP4 URL: {result['videoPaths'].get('mp4', 'N/A')}")
return result
def generate_video_veo31(
prompt: str,
image_url: str
) -> Dict[str, Any]:
"""
Example 3: Generate high-quality video with VEO 3.1.
VEO 3.1 produces the highest quality videos at 8 seconds duration.
Args:
prompt: Text description of the video motion
image_url: URL of the source image
Returns:
The completed video data
"""
print("=== VEO 3.1 Video Generation ===\n")
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"imageUrl": image_url,
"settings": {
"mode": "veo31"
# VEO 3.1 has fixed 8-second duration
}
}
)
response.raise_for_status()
data = response.json()
video_id = data["id"]
print(f"VEO 3.1 generation started. ID: {video_id}")
print("Note: VEO 3.1 may take longer to generate...")
# VEO can take longer, increase polling time
result = poll_status("videos", video_id, interval_seconds=5.0, max_attempts=120)
print(f"\nVideo ready!")
print(f"MP4 URL: {result['videoPaths'].get('mp4', 'N/A')}")
print(f"Credits used: {result.get('creditsUsed', 'N/A')} (VEO 3.1 uses 1500-6000 credits)")
return result
def generate_video_kling(
prompt: str,
image_url: str,
duration: int = 10
) -> Dict[str, Any]:
"""
Example 4: Generate video with Kling 2.6.
Kling 2.6 offers a good balance of quality and duration (5-10 seconds).
Args:
prompt: Text description of the video motion
image_url: URL of the source image
duration: Video duration (5-10 seconds)
Returns:
The completed video data
"""
print("=== Kling 2.6 Video Generation ===\n")
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"imageUrl": image_url,
"settings": {
"mode": "kling26",
"duration": min(max(duration, 5), 10) # Clamp to 5-10
}
}
)
response.raise_for_status()
data = response.json()
video_id = data["id"]
print(f"Kling 2.6 generation started. ID: {video_id}")
result = poll_video_status(video_id)
print(f"\nVideo ready!")
print(f"MP4 URL: {result['videoPaths'].get('mp4', 'N/A')}")
return result
def generate_video_wan(
prompt: str,
image_url: str,
duration: int = 5
) -> Dict[str, Any]:
"""
Example 5: Generate video with Wan 2.5.
Wan 2.5 is good for smooth motion effects.
Args:
prompt: Text description of the video motion
image_url: URL of the source image
duration: Video duration (5-10 seconds)
Returns:
The completed video data
"""
print("=== Wan 2.5 Video Generation ===\n")
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"imageUrl": image_url,
"settings": {
"mode": "wan25",
"duration": duration
}
}
)
response.raise_for_status()
data = response.json()
video_id = data["id"]
print(f"Wan 2.5 generation started. ID: {video_id}")
result = poll_video_status(video_id)
print(f"\nVideo ready!")
print(f"MP4 URL: {result['videoPaths'].get('mp4', 'N/A')}")
return result
# =============================================================================
# Complete Workflow Examples
# =============================================================================
def complete_image_to_video_workflow():
"""
Example 6: Complete workflow - generate image then video.
This demonstrates the full workflow of:
1. Generating a source image
2. Creating a video from that image
"""
print("=" * 60)
print("COMPLETE IMAGE-TO-VIDEO WORKFLOW")
print("=" * 60 + "\n")
# Step 1: Generate source image
image_result = generate_source_image(
"A majestic lion standing on a rock at sunset, golden savanna in background"
)
image_id = image_result["id"]
print("\n" + "-" * 40 + "\n")
# Step 2: Generate video from the image
video_result = generate_video_from_image_id(
prompt="The lion slowly turns its head and looks at the camera, wind blowing through its mane",
image_completion_id=image_id,
mode="kling26",
duration=5
)
print("\n" + "=" * 60)
print("WORKFLOW COMPLETE")
print("=" * 60)
print(f"Source Image: {image_result['imageVersions']['original']}")
print(f"Video: {video_result['videoPaths'].get('mp4', 'N/A')}")
return {
"image": image_result,
"video": video_result
}
def compare_video_models(image_url: str):
"""
Example 7: Compare different video models.
Generates videos with different models from the same source image.
Args:
image_url: URL of the source image
"""
print("=" * 60)
print("COMPARING VIDEO MODELS")
print("=" * 60 + "\n")
prompt = "Gentle camera pan with subtle motion, cinematic"
models = [
{"mode": "default", "duration": 4},
{"mode": "kling26", "duration": 5},
# Uncomment for VEO (expensive):
# {"mode": "veo31"},
]
results = {}
for config in models:
mode = config["mode"]
print(f"\n--- Testing {mode} ---\n")
try:
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"imageUrl": image_url,
"settings": config
}
)
response.raise_for_status()
data = response.json()
video_id = data["id"]
result = poll_video_status(video_id)
results[mode] = {
"url": result["videoPaths"].get("mp4", "N/A"),
"credits": result.get("creditsUsed", "N/A")
}
except Exception as e:
print(f"Error with {mode}: {e}")
results[mode] = {"error": str(e)}
print("\n" + "=" * 60)
print("COMPARISON RESULTS")
print("=" * 60)
for mode, data in results.items():
print(f"\n{mode}:")
if "error" in data:
print(f" Error: {data['error']}")
else:
print(f" URL: {data['url']}")
print(f" Credits: {data['credits']}")
return results
# =============================================================================
# Error Handling Example
# =============================================================================
def generate_video_with_error_handling(
prompt: str,
image_url: str,
mode: str = "default"
) -> Optional[Dict[str, Any]]:
"""
Example 8: Video generation with comprehensive error handling.
Args:
prompt: Text description of the video motion
image_url: URL of the source image
mode: Video model
Returns:
The completed video data, or None if failed
"""
print("=== Video Generation with Error Handling ===\n")
try:
response = requests.post(
f"{API_BASE_URL}/videos",
headers=HEADERS,
json={
"prompt": prompt,
"imageUrl": image_url,
"settings": {"mode": mode}
}
)
# Handle HTTP errors
if response.status_code == 401:
raise ValueError("Invalid API key. Check your credentials at letz.ai/subscription")
elif response.status_code == 402:
raise ValueError("Insufficient credits. Top up at letz.ai/subscription")
elif response.status_code == 400:
error_data = response.json()
raise ValueError(f"Invalid parameters: {error_data.get('error', 'Unknown')}")
elif response.status_code == 429:
raise ValueError("Rate limited. Please wait before making more requests.")
response.raise_for_status()
data = response.json()
video_id = data["id"]
print(f"Video generation started. ID: {video_id}")
result = poll_video_status(video_id)
print(f"\nVideo ready!")
print(f"MP4 URL: {result['videoPaths'].get('mp4', 'N/A')}")
return result
except requests.exceptions.ConnectionError:
print("Error: Could not connect to LetzAI API. Check your internet connection.")
return None
except requests.exceptions.Timeout:
print("Error: Request timed out. Try again later.")
return None
except ValueError as e:
print(f"Error: {e}")
return None
except RuntimeError as e:
print(f"Generation failed: {e}")
return None
except TimeoutError as e:
print(f"Timeout: {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
# =============================================================================
# Main Entry Point
# =============================================================================
def main():
"""Run example demonstrations."""
# First, generate a source image
print("First, we need a source image for video generation...\n")
try:
image_result = generate_source_image(
"A serene mountain lake at dawn with mist rising from the water"
)
image_url = image_result["imageVersions"]["original"]
print("\n" + "=" * 60 + "\n")
# Generate a video from the image
generate_video_from_url(
prompt="Gentle ripples spread across the lake as morning light intensifies",
image_url=image_url,
mode="default",
duration=4
)
# Uncomment to run other examples:
# generate_video_kling(prompt="...", image_url=image_url)
# generate_video_veo31(prompt="...", image_url=image_url)
# complete_image_to_video_workflow()
except Exception as e:
print(f"Error: {e}")
return 1
return 0
if __name__ == "__main__":
exit(main())
MIT License
Copyright (c) 2025 LetzAI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
LetzAI API Skill for Claude
A Claude skill that enables AI-powered image and video generation using the LetzAI API.
What is This?
This repository contains a Claude skill that teaches Claude how to effectively use the LetzAI API. When this skill is active, Claude can help you:
- Generate AI images using multiple base models (Nano Banana Pro, Flux2 Max, SeeDream)
- Create AI videos from images using VEO, Kling, and Wan models
- Edit images with context-aware AI editing
- Upscale images to higher resolutions
- Use custom trained models with the
@modelnamesyntax
Repository Structure
letzai-skill/
├── SKILL.md # Main skill file (required by Claude)
├── api_reference.md # Detailed API documentation
├── examples/
│ ├── image_generation.js # JavaScript examples
│ ├── video_generation.py # Python examples
│ └── polling_pattern.md # Async polling guide
├── LICENSE.txt # MIT License
└── README.md # This fileQuick Start
Prerequisites
1. Get your API key at letz.ai/subscription 2. Set up authentication with Bearer token
Installation
Skills CLI (Recommended)
Install with a single command using skills.sh:
npx skills add Letz-AI/letzai-skillClaude Code
Place this skill in your project's .claude/skills/letzai-api/ directory.
Claude.ai
Go to Settings → Features → Add custom skill (requires Pro/Max/Team/Enterprise).
Claude API
Upload via the Skills API. See Anthropic's Skills documentation for details.
Usage Examples
Once the skill is installed, you can ask Claude things like:
- "Generate an image of a sunset over the ocean using LetzAI"
- "Create a video from this image with the camera slowly panning"
- "Edit this photo to change the background to a beach"
- "List my trained models and generate an image with @john_doe"
- "Upscale this image to 4K"
API Overview
| Endpoint | Purpose |
|---|---|
POST /images | Generate images |
POST /videos | Generate videos from images |
POST /image-edits | Edit existing images |
POST /upscales | Upscale images |
GET /models | List custom trained models |
All generation is asynchronous - poll the GET endpoints until status is "ready".
Available Base Models
Image Generation
| Model | API Value | Resolutions |
|---|---|---|
| Nano Banana Pro | gemini-3-pro-image-preview | default, 2k, 4k |
| Flux2 Max | flux2-max | 1k, hd |
| SeeDream 4.5 | seedream-4-5-251128 | 2k, 4k |
Video Generation
| Model | API Value | Duration |
|---|---|---|
| Default | default | 2-6 sec |
| VEO 3.1 | veo31 | 8 sec |
| Kling 2.6 | kling26 | 5-10 sec |
| Wan 2.5 | wan25 | 5-10 sec |
Custom Trained Models
LetzAI supports custom trained models for persons, objects, and styles. Use them in prompts with the @modelname syntax:
@john_doe on the beach at sunset
A product photo featuring @my_product
Portrait in @vintage_style aestheticTrain models via the LetzAI web interface.
Resources
- LetzAI Website: www.letz.ai
- API Documentation: api.letz.ai/doc
- Developer Docs: letz.ai/docs/api
- Get API Key: letz.ai/subscription
License
This skill is released under the MIT License. See LICENSE.txt for details.
Disclaimer
This skill is provided to help integrate with the LetzAI API. Always test thoroughly in your own environment. API behavior and pricing may change - refer to the official LetzAI documentation for the most current information.
Related skills
How it compares
Pick letzai-api when you need LetzAI-specific @modelname, upscaling, and multi-model image/video endpoints rather than generic OpenAI image API wrappers.
FAQ
Which LetzAI image and video models does letzai-api cover?
letzai-api documents Nano Banana Pro, Flux2 Max, and SeeDream for images, plus VEO and Kling for video generation through the LetzAI REST API.
What dependencies does letzai-api require?
letzai-api lists node-fetch for npm-based Node.js clients and requests for pip-based Python clients when calling LetzAI authentication and generation endpoints.
Is Letzai Api safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.