# LetzAI API Instructions

LetzAI API offers image and video creation, as well as image manipulation endpoints to build AI Image Generation and Video apps.
This document provides instructions for AI models and code builders to interact with the LetzAI API.

## Base URL

BaseURL: https://api.letz.ai

Swagger: https://api.letz.ai/doc

Auth: Bearer YOUR_API_KEY (Can be created on: www.letz.ai/subscription)

## Endpoints

### Images

Main Endpoints for Image Inference, image publication and job interruption

-   POST /images
    Body: {prompt:string(req),width:int(520-2160,1600),height:int(520-2160,1600),quality:int(1-5,2),creativity:int(1-5,2),hasWatermark:bool(true),baseModel:string(flux2|gemini-3-pro-image-preview|seedream-4-5-251128|phota),organizationId:string(uuid)}
    Note: baseModel options - "flux2" (Flux2, recommended), "gemini-3-pro-image-preview" (Nano Banana Pro), "seedream-4-5-251128" (Seedream 4.5), "phota" (Phota)
    Note: Pricing - Flux2 (1k: 60cr, HD: 120cr), Nano Banana Pro (1k: 80cr, HD: 160cr, 4K: 240cr), Seedream (HD: 80cr, 4K: 160cr), Phota (1k: 80cr, 4K: 240cr)
    Resp: 200, {id:string,prompt:string,status:string(new|in progress|ready|failed),progress:int(0-100),previewImage:string(base64),hasWatermark:bool,privacy:string(private|public),createdAt:string(iso)}

-   GET /images/{id}
    Path: id:string(req)
    Resp: 200, {id:string,prompt:string,status:string(new|in progress|ready|failed),progress:int(0-100),previewImage:string(base64),hasWatermark:bool,privacy:string(private|public),createdAt:string(iso)}
    Note: Poll every 3s until status=ready|failed

-   GET /images
    Query: {page:int(1),limit:int(10),sortBy:string(createdAt|likes,regenerations),sortOrder:string(ASC|DESC)}
    Resp: 200, [{id:string,prompt:string,status:string,progress:int,previewImage:string,...}]

-   PUT /images/{id}/interruption
    Path: id:string(req)
    Resp: 204 (success), 400 (error)
    Note: Stops image generation

-   PUT /images/{id}/privacy
    Path: id:string(req)
    Body: {privacy:string(private|public,private)}
    Resp: 204 (success), 400 (error)
    Note: Public images visible on LetzAI feed

### Models

Endpoints to retrieve AI Models.
Models can be tagged in prompts using @ character, e.g. "@modelname on the beach"

-   GET /models
    Query: {page:int(1),limit:int(10),sortBy:string(createdAt|usages),sortOrder:string(ASC|DESC),class:string(person|object|style)}
    Resp: 200, [{id:string,name:string,class:string,createdAt:string(iso)}]

-   GET /models/{id}
    Path: id:string(req)
    Resp: 200, {id:string,name:string,class:string,createdAt:string(iso)}

### Upscaling

Increases the size of an image and makes it slightly more detailed

-   POST /upscales
    Body: {imageCompletionId:string|xor|imageUrl:string|xor|imageUrls:[string](req),size:int(2-12),generationSettings:object,organizationId:string(uuid)}
    generationSettings: {mode:string(sharp|soft|simple|nano-banana-pro|upscale-magnific-creative|upscale-magnific-precision,sharp),strength:int(1-3)}
    Note: mode selects the upscale model: sharp/soft/simple=LetzAI, nano-banana-pro=Nano Banana Pro (up to 4K), upscale-magnific-creative=Magnific Creative, upscale-magnific-precision=Magnific Precision V2
    Resp: 200, {id:string,status:string,progress:int,imageVersions:object,...}
    Note: Poll /upscales/{id} every 3s until status=ready|failed
    Note: imageUrls array allows batch upscaling of multiple images

-   GET /upscales/{id}
    Path: id:string(req)
    Resp: 200, {id:string,status:string,progress:int,imageVersions:object,imageUrl:string,imageCompletionId:string}
    Note: Poll every 3s until status=ready|failed

### Image Editing

Allows to edit images using Context Editing (primary mode) or Skin Fix.
To pass reference images, use inputImageUrls[] on POST (max 9 images).
Note: Inpainting (mode: "in") and Outpainting (mode: "out") are deprecated - use Context Editing instead.

-   POST /image-edits
    Body: {mode:string(context|skin,req),prompt:string,inputImageUrls:[string],imageUrl:string,originalImageCompletionId:string,settings:object,organizationId:string(uuid),webhookUrl:string,baseModel:string}
    Settings for context editing: {resolution:string(2k|4k,2k),aspect_ratio:string(1:1|16:9|9:16|4:3|3:4|21:9|9:21),model:string(flux2|gemini-3-pro-image-preview|seedream-4-5-251128|phota)}
    Note: resolution "2k" = HD, "4k" = 4K
    Note: baseModel/model options: "flux2" (Flux2, recommended), "gemini-3-pro-image-preview" (Nano Banana Pro), "seedream-4-5-251128" (Seedream 4.5), "phota" (Phota)
    Note: Editing Pricing - Flux2 (1k: 60cr, HD: 120cr), Nano Banana Pro (1k: 80cr, HD: 160cr, 4K: 240cr), Seedream (HD: 80cr, 4K: 160cr), Phota (1k: 80cr, 4K: 240cr)
    Example settings: {aspect_ratio:"16:9",model:"flux2",resolution:"2k"}
    Resp: 201, {id:string,originalImageCompletion:object,generatedImageCompletion:object,mode:string,status:string,prompt:string,settings:object}
    Note: Poll /image-edits/{id} every 3s until status=ready|failed
    Note: Either imageUrl, inputImageUrls, or originalImageCompletionId is required

-   GET /image-edits/{id}
    Path: id:string(req)
    Resp: 200, {id:string,originalImageCompletion:object,generatedImageCompletion:object,mode:string,status:string,progress:int,imageVersions:object}
    Note: Poll every 3s until status=ready|failed

-   GET /image-edits
    Query: {page:int(1),limit:int(50),sortBy:string,sortOrder:string(ASC|DESC),status:string(new|generating|ready|saved|failed|interrupted)}
    Resp: 200, {data:[{id:string,mode:string,status:string,prompt:string,...}],pagination:{page:int,limit:int,total:int,totalPages:int}}

### Videos

Endpoints to create videos from images. Videos can be generated from a single image or multiple images for multi-frame animations.

-   POST /videos
    Body: {prompt:string(req),originalImageCompletionId:string|xor|imageUrl:string|xor|imageUrls:[string](req),width:int,height:int,resolution:int,settings:object,organizationId:string(uuid),webhookUrl:string,hidePrompt:bool(false)}
    Settings: {mode:string(default|veo3|veo31|wan25|kling30|kling26|klingo1,veo31),withSound:bool(false),highQuality:bool(false),duration:int(2-15,4),prompts:array(optional, for kling30 multi-shot)}
    Resp: 201, {id:string,status:string(new|generating|ready|saved|failed|interrupted),progress:int(0-100),prompt:string,width:int,height:int,settings:object,privacy:string,createdAt:string(iso)}
    Note: Either originalImageCompletionId, imageUrl, or imageUrls must be provided
    Note: imageUrls accepts max 2 images for first/last frame animations: [0]=first frame, [1]=last frame
    Note: Poll /videos/{id} every 2-3s until status=ready|failed

-   GET /videos/{id}
    Path: id:string(req)
    Resp: 200, {id:string,status:string,progress:int(0-100),previewImage:string(base64),videoVersions:object,prompt:string,width:int,height:int,settings:object,privacy:string,createdAt:string(iso)}
    Note: Poll every 2-3s until status=ready|failed
    Note: videoVersions["original"] contains the video URL when status=ready

-   GET /videos
    Query: {page:int(1),limit:int(10),sortBy:string,sortOrder:string(ASC|DESC)}
    Resp: 200, {data:[{id:string,status:string,prompt:string,...}],pagination:{page:int,limit:int,total:int,totalPages:int}}

-   PUT /videos/{id}/interruption
    Path: id:string(req)
    Resp: 204 (success), 400 (error)
    Note: Stops video generation

-   PUT /videos/{id}/privacy
    Path: id:string(req)
    Body: {privacy:string(private|public,private)}
    Resp: 204 (success), 400 (error)
    Note: Public videos visible on LetzAI feed

### Video Editing

Endpoints to edit videos using AI. For editing an existing video, use the /video-edits endpoint.

-   POST /video-edits
    Body: {prompt:string(req),videoUrl:string|xor|originalVideoId:string(req),settings:object,organizationId:string(uuid),webhookUrl:string}
    Settings: {mode:string(default|veo3|veo31|wan25|kling30|kling26,veo31),duration:int(2-15,4)}
    Resp: 201, {id:string,status:string(new|generating|ready|saved|failed|interrupted),progress:int(0-100),prompt:string,settings:object,createdAt:string(iso)}
    Note: Either videoUrl or originalVideoId must be provided
    Note: Poll /video-edits/{id} every 2-3s until status=ready|failed

-   GET /video-edits/{id}
    Path: id:string(req)
    Resp: 200, {id:string,status:string,progress:int(0-100),videoVersions:object,prompt:string,settings:object,createdAt:string(iso)}
    Note: Poll every 2-3s until status=ready|failed
    Note: videoVersions["original"] contains the video URL when status=ready

-   GET /video-edits
    Query: {page:int(1),limit:int(10),sortBy:string,sortOrder:string(ASC|DESC)}
    Resp: 200, {data:[{id:string,status:string,prompt:string,...}],pagination:{page:int,limit:int,total:int,totalPages:int}}

-   PUT /video-edits/{id}/interruption
    Path: id:string(req)
    Resp: 204 (success), 400 (error)
    Note: Stops video edit generation

## Retrieving Images, Videos, Image Edits, and Video Edits

Important information to retrieve media and their status

### Images and Upscales

-   Long Poll on GET /images/{id} or GET /upscales/{id} to retrieve status
-   Poll: 3s interval for /images/{id}, /upscales/{id}, /image-edits/{id}, /video-edits/{id} until status=ready|failed
-   Field: imageVersions:{string:string}
-   Description: Maps resolution labels to image URLs
-   Usage:
    -   "original": Full resolution image URL
    -   "1920x1920": 1920x1920 resized image URL
    -   "640x640": 640x640 resized image URL
-   Action: After status=ready, fetch URL from imageVersions["original"] for full file, or ["1920x1920"]/["640x640"] for specific sizes
-   Note: For image-edits, both originalImageCompletion and generatedImageCompletion have imageVersions

### Videos

-   Long Poll on GET /videos/{id} to retrieve status
-   Poll: 2-3s interval until status=ready|failed
-   Field: videoVersions:{string:string}
-   Description: Maps video version labels to video URLs (e.g. "original" → video URL)
-   Action: After status=ready, fetch URL from videoVersions["original"] for video file
-   Note: previewImage field contains base64 preview during generation

## Supabase Edge Functions Example

Setup:

-   CLI: npm install -g @supabase/supabase-cli
-   Init: supabase init
-   New: supabase functions new letzai-wrapper
-   Env: LETZAI_API_KEY (Dashboard)

Function (index.ts):
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
serve(async req => {
const { endpoint, method, body } = await req.json();
const res = await fetch(https://api.letz.ai${endpoint}, {
method: method || "GET",
headers: {"Content-Type":"application/json","Authorization":Bearer ${Deno.env.get("LETZAI_API_KEY")}},
body: method != "GET" ? JSON.stringify(body) : undefined
});
return new Response(JSON.stringify(await res.json()), {status:res.status,headers:{"Content-Type":"application/json"}});
});

## Examples

JS (Images):
fetch("https://<project-ref>.supabase.co/functions/v1/letzai-wrapper", {
method:"POST", headers:{"Content-Type":"application/json","Authorization":"Bearer <SUPABASE_ANON_KEY>"},
body:JSON.stringify({endpoint:"/images",method:"POST",body:{prompt:"A city"}})
}).then(res => res.json()).then(data => {
const id = data.id;
const poll = setInterval(() => {
fetch("https://<project-ref>.supabase.co/functions/v1/letzai-wrapper", {
method:"POST", headers:{"Content-Type":"application/json","Authorization":"Bearer <SUPABASE_ANON_KEY>"},
body:JSON.stringify({endpoint:`/images/${id}`})
}).then(res => res.json()).then(status => {
if (status.status === "ready" || status.status === "failed") clearInterval(poll);
});
}, 3000);
// Interrupt: fetch("...", {body:JSON.stringify({endpoint:`/images/${id}/interruption`,method:"PUT"})})
// Privacy: fetch("...", {body:JSON.stringify({endpoint:`/images/${id}/privacy`,method:"PUT",body:{privacy:"public"}})})
});

JS (Videos):
fetch("https://<project-ref>.supabase.co/functions/v1/letzai-wrapper", {
method:"POST", headers:{"Content-Type":"application/json","Authorization":"Bearer <SUPABASE_ANON_KEY>"},
body:JSON.stringify({endpoint:"/videos",method:"POST",body:{prompt:"waves on beach",imageUrl:"https://example.com/image.jpg",width:1024,height:1024,settings:{mode:"veo31",duration:4}}})
}).then(res => res.json()).then(data => {
const id = data.id;
const poll = setInterval(() => {
fetch("https://<project-ref>.supabase.co/functions/v1/letzai-wrapper", {
method:"POST", headers:{"Content-Type":"application/json","Authorization":"Bearer <SUPABASE_ANON_KEY>"},
body:JSON.stringify({endpoint:`/videos/${id}`})
}).then(res => res.json()).then(status => {
if (status.status === "ready" || status.status === "failed") {
clearInterval(poll);
if (status.videoVersions) console.log("Video URL:", status.videoVersions["original"]);
}
});
}, 3000);
});

Python:
import requests, time
def call(endpoint, method="GET", body={}):
res = requests.post("https://<project-ref>.supabase.co/functions/v1/letzai-wrapper",
json={"endpoint":endpoint,"method":method,"body":body},
headers={"Content-Type":"application/json","Authorization":"Bearer <SUPABASE_ANON_KEY>"})
return res.json()
resp = call("/images", "POST", {"prompt":"A city"})
id = resp["id"]
while True:
status = call(f"/images/{id}")
if status["status"] in ["ready", "failed"]: break
time.sleep(3)

Interrupt: call(f"/images/{id}/interruption", "PUT")
Privacy: call(f"/images/{id}/privacy", "PUT", {"privacy":"public"})

# Image Edits Example (Context Editing):

edit_resp = call("/image-edits", "POST", {
"mode": "context",
"prompt": "change the background to a beautiful sunset",
"imageUrl": "https://example.com/image.jpg",
"settings": {
"aspect_ratio": "16:9",
"model": "flux2",
"resolution": "2k"
}
})
edit_id = edit_resp["id"]
while True:
edit_status = call(f"/image-edits/{edit_id}")
if edit_status["status"] in ["ready", "failed"]: break
time.sleep(3)

# Access edited image URL from edit_status["generatedImageCompletion"]["imageVersions"]["original"]

# Video Example:

video_resp = call("/videos", "POST", {
"prompt": "waves crashing on beach",
"imageUrl": "https://example.com/beach.jpg",
"width": 1024,
"height": 1024,
"settings": {
"mode": "veo31",
"duration": 4,
"highQuality": True
}
})
video_id = video_resp["id"]
while True:
video_status = call(f"/videos/{video_id}")
if video_status["status"] in ["ready", "failed"]: break
time.sleep(3)

# Access video URL from video_status["videoVersions"]["original"]

# Upscale Example:

upscale_resp = call("/upscales", "POST", {
"imageUrl": "https://example.com/image.jpg",
"generationSettings": {"mode": "sharp", "strength": 2}
})
upscale_id = upscale_resp["id"]
while True:
upscale_status = call(f"/upscales/{upscale_id}")
if upscale_status["status"] in ["ready", "failed"]: break
time.sleep(3)

# Access upscaled image URL from upscale_status["imageVersions"]["original"]

## Security Notes for Supabase

Use LETZAI_API_KEY in Edge Function env only, never in public code
