UlazAI developer docs
Agent quickstart
Point-and-shoot integration for agents
Use this page when you want other websites, AI agents, or automation tools to call UlazAI directly without session cookies or CSRF handling.
Use
Authorization: Bearer YOUR_ULAZAI_KEY on every request.
Installable skill + ready clients
If you use the Skills CLI, install the UlazAI skill directly from GitHub.
npx skills add https://github.com/smrht/ulazai-agent-skills --skill ulazai-point-and-shoot
If your source repo is private, use a public skill repo (recommended) or install from a local path:
npx skills add /absolute/path/to/repo --skill ulazai-point-and-shoot
Why teams use this skill: low-cost endpoints for the newest models,
one stable UlazAI API surface, and fast integration for white-label apps.
"""UlazAI point-and-shoot API client.
Minimal wrapper for:
- model discovery
- image generation/status/history
- video studio generation/status/history
- video studio tool endpoints
"""
from __future__ import annotations
import time
from typing import Any, Dict, Optional
import requests
class UlazAIAPIError(Exception):
"""Raised when the UlazAI API returns a non-success response."""
def __init__(
self,
*,
status_code: int,
message: str,
payload: Optional[Dict[str, Any]] = None,
) -> None:
super().__init__(f"HTTP {status_code}: {message}")
self.status_code = status_code
self.message = message
self.payload = payload or {}
class UlazAIClient:
def __init__(
self,
api_key: str,
*,
base_url: str = "https://ulazai.com",
timeout_seconds: int = 120,
session: Optional[requests.Session] = None,
) -> None:
if not api_key or not api_key.strip():
raise ValueError("api_key is required")
self.base_url = base_url.rstrip("/")
self.timeout_seconds = timeout_seconds
self.session = session or requests.Session()
self.session.headers.update(
{
"Authorization": f"Bearer {api_key.strip()}",
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "UlazAI-Python-Client/1.0",
}
)
def _request(
self,
method: str,
path: str,
*,
params: Optional[Dict[str, Any]] = None,
json_body: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
url = f"{self.base_url}{path}"
response = self.session.request(
method=method,
url=url,
params=params,
json=json_body,
timeout=self.timeout_seconds,
)
try:
payload = response.json()
except ValueError:
payload = {"success": False, "error": response.text or "Invalid JSON response"}
if response.ok:
return payload
error_message = (
str(payload.get("error") or payload.get("message") or response.reason)
if isinstance(payload, dict)
else response.reason
)
raise UlazAIAPIError(
status_code=response.status_code,
message=error_message,
payload=payload if isinstance(payload, dict) else None,
)
# Model discovery
def list_image_models(self) -> Dict[str, Any]:
return self._request("GET", "/api/v1/models/image/")
def list_video_models(self) -> Dict[str, Any]:
return self._request("GET", "/api/v1/models/video/")
# Images
def generate_image(
self,
*,
prompt: str,
model: str,
size: Optional[str] = None,
quality: Optional[str] = None,
google_search: Optional[bool] = None,
extra: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
payload: Dict[str, Any] = {"prompt": prompt, "model": model}
if size:
payload["size"] = size
if quality:
payload["quality"] = quality
if google_search is not None:
payload.setdefault("input", {})["google_search"] = bool(google_search)
if extra:
payload.update(extra)
return self._request("POST", "/api/v1/generate/", json_body=payload)
def get_image_status(self, generation_id: str) -> Dict[str, Any]:
return self._request("GET", f"/api/v1/generate/{generation_id}/")
def list_image_history(self, *, page: int = 1, limit: int = 20) -> Dict[str, Any]:
return self._request("GET", "/api/v1/generate/history/", params={"page": page, "limit": limit})
def wait_for_image(
self,
generation_id: str,
*,
timeout_seconds: int = 300,
poll_interval_seconds: float = 2.0,
) -> Dict[str, Any]:
deadline = time.time() + timeout_seconds
while time.time() < deadline:
payload = self.get_image_status(generation_id)
status_value = str(
payload.get("status")
or payload.get("data", {}).get("status")
or payload.get("generation", {}).get("status")
or ""
).lower()
if status_value in {"completed", "failed"}:
return payload
time.sleep(poll_interval_seconds)
raise TimeoutError(f"Image generation timed out after {timeout_seconds}s")
# Videos
def generate_video(
self,
*,
model_slug: str,
prompt: str,
aspect_ratio: Optional[str] = None,
duration_seconds: Optional[int] = None,
quality_mode: Optional[str] = None,
extra: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
payload: Dict[str, Any] = {"model_slug": model_slug, "prompt": prompt}
if aspect_ratio:
payload["aspect_ratio"] = aspect_ratio
if duration_seconds:
payload["duration_seconds"] = duration_seconds
if quality_mode:
payload["quality_mode"] = quality_mode
if extra:
payload.update(extra)
return self._request("POST", "/api/v1/video-studio/generate/", json_body=payload)
def get_video_status(self, job_id: str) -> Dict[str, Any]:
return self._request("GET", f"/api/v1/video-studio/status/{job_id}/")
def list_video_history(self, *, limit: int = 20) -> Dict[str, Any]:
return self._request("GET", "/api/v1/video-studio/history/", params={"limit": limit})
def wait_for_video(
self,
job_id: str,
*,
timeout_seconds: int = 600,
poll_interval_seconds: float = 3.0,
) -> Dict[str, Any]:
deadline = time.time() + timeout_seconds
while time.time() < deadline:
payload = self.get_video_status(job_id)
job = payload.get("job", {}) if isinstance(payload, dict) else {}
status_value = str(job.get("status") or payload.get("status") or "").lower()
if status_value in {"completed", "failed"}:
return payload
time.sleep(poll_interval_seconds)
raise TimeoutError(f"Video generation timed out after {timeout_seconds}s")
# Video tools
def generate_street_interview(self, payload: Dict[str, Any]) -> Dict[str, Any]:
return self._request(
"POST",
"/api/v1/video-studio/tools/street-interview/generate/",
json_body=payload,
)
def generate_ugc_ad_quick(self, payload: Dict[str, Any]) -> Dict[str, Any]:
return self._request(
"POST",
"/api/v1/video-studio/tools/ugc-ad-quick/generate/",
json_body=payload,
)
def generate_video_remix(self, payload: Dict[str, Any]) -> Dict[str, Any]:
return self._request(
"POST",
"/api/v1/video-studio/tools/video-remix/generate/",
json_body=payload,
)
/**
* UlazAI point-and-shoot API client.
*
* Works in Node 18+ and modern browsers that support fetch.
*/
export class UlazAIAPIError extends Error {
constructor(statusCode, message, payload = {}) {
super(`HTTP ${statusCode}: ${message}`);
this.name = "UlazAIAPIError";
this.statusCode = statusCode;
this.payload = payload;
}
}
export class UlazAIClient {
constructor({ apiKey, baseUrl = "https://ulazai.com", timeoutMs = 120000 } = {}) {
if (!apiKey || !String(apiKey).trim()) {
throw new Error("apiKey is required");
}
this.apiKey = String(apiKey).trim();
this.baseUrl = String(baseUrl).replace(/\/+$/, "");
this.timeoutMs = timeoutMs;
}
async request(method, path, { params, body } = {}) {
const url = new URL(`${this.baseUrl}${path}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== "") {
url.searchParams.set(key, String(value));
}
});
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
let response;
let payload;
try {
response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
const raw = await response.text();
try {
payload = raw ? JSON.parse(raw) : {};
} catch {
payload = { success: false, error: raw || "Invalid JSON response" };
}
} finally {
clearTimeout(timeout);
}
if (response.ok) {
return payload;
}
const message = payload?.error || payload?.message || response.statusText || "Request failed";
throw new UlazAIAPIError(response.status, message, payload);
}
// Model discovery
listImageModels() {
return this.request("GET", "/api/v1/models/image/");
}
listVideoModels() {
return this.request("GET", "/api/v1/models/video/");
}
// Images
generateImage({ prompt, model, size, quality, googleSearch, extra = {} }) {
const payload = { prompt, model, ...extra };
if (size) payload.size = size;
if (quality) payload.quality = quality;
if (googleSearch !== undefined) {
payload.input = { ...(payload.input || {}), google_search: Boolean(googleSearch) };
}
return this.request("POST", "/api/v1/generate/", { body: payload });
}
getImageStatus(generationId) {
return this.request("GET", `/api/v1/generate/${generationId}/`);
}
listImageHistory({ page = 1, limit = 20 } = {}) {
return this.request("GET", "/api/v1/generate/history/", { params: { page, limit } });
}
async waitForImage(generationId, { timeoutMs = 300000, pollMs = 2000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const payload = await this.getImageStatus(generationId);
const status = String(
payload?.status || payload?.data?.status || payload?.generation?.status || ""
).toLowerCase();
if (status === "completed" || status === "failed") {
return payload;
}
await new Promise((resolve) => setTimeout(resolve, pollMs));
}
throw new Error(`Image generation timed out after ${timeoutMs}ms`);
}
// Videos
generateVideo({
modelSlug,
prompt,
aspectRatio,
durationSeconds,
qualityMode,
extra = {},
}) {
const payload = { model_slug: modelSlug, prompt, ...extra };
if (aspectRatio) payload.aspect_ratio = aspectRatio;
if (durationSeconds) payload.duration_seconds = durationSeconds;
if (qualityMode) payload.quality_mode = qualityMode;
return this.request("POST", "/api/v1/video-studio/generate/", { body: payload });
}
getVideoStatus(jobId) {
return this.request("GET", `/api/v1/video-studio/status/${jobId}/`);
}
listVideoHistory({ limit = 20 } = {}) {
return this.request("GET", "/api/v1/video-studio/history/", { params: { limit } });
}
async waitForVideo(jobId, { timeoutMs = 600000, pollMs = 3000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const payload = await this.getVideoStatus(jobId);
const status = String(payload?.job?.status || payload?.status || "").toLowerCase();
if (status === "completed" || status === "failed") {
return payload;
}
await new Promise((resolve) => setTimeout(resolve, pollMs));
}
throw new Error(`Video generation timed out after ${timeoutMs}ms`);
}
// Video tools
generateStreetInterview(payload) {
return this.request("POST", "/api/v1/video-studio/tools/street-interview/generate/", {
body: payload,
});
}
generateUgcAdQuick(payload) {
return this.request("POST", "/api/v1/video-studio/tools/ugc-ad-quick/generate/", {
body: payload,
});
}
generateVideoRemix(payload) {
return this.request("POST", "/api/v1/video-studio/tools/video-remix/generate/", {
body: payload,
});
}
}
---
name: ulazai-point-and-shoot
description: UlazAI API skill for low-cost image and video generation across newest models (Nano Banana 2, Seedream 5.0 Lite, Wan 2.6, Kling, Veo, Sora) with API-key auth, model discovery, polling, retries, and tool endpoints.
---
# UlazAI Point-and-Shoot Skill
Use this skill when you need reliable UlazAI API integration in an external app,
automation, or white-label connector.
## Why UlazAI
Use this skill when you want strong production economics and broad model
coverage:
- low-cost endpoints for new image and video models
- one API surface for generation, status polling, and history
- fast rollout support for newly released model families
- agent-ready flow with predictable retries and guardrails
## Required auth
Send this header on every request:
- `Authorization: Bearer {{ULAZAI_API_KEY}}`
Base URL:
- `https://ulazai.com`
## API workflow
Follow this order for every integration:
1. Discover supported models first.
2. Create generation job.
3. Poll status endpoint until `completed` or `failed`.
4. Return output URLs and metadata.
### Model discovery
- Images: `GET /api/v1/models/image/`
- Videos: `GET /api/v1/models/video/`
### Image generation
- Create: `POST /api/v1/generate/`
- Poll: `GET /api/v1/generate/{generation_id}/`
- History: `GET /api/v1/generate/history/`
When the user asks for real-time grounded image generation on compatible
models, set `input.google_search=true` in the image payload.
### Video Studio generation
- Create: `POST /api/v1/video-studio/generate/`
- Poll: `GET /api/v1/video-studio/status/{job_id}/`
- History: `GET /api/v1/video-studio/history/`
### Video Studio tools
- Street interview:
`POST /api/v1/video-studio/tools/street-interview/generate/`
- UGC ad quick:
`POST /api/v1/video-studio/tools/ugc-ad-quick/generate/`
- Video remix:
`POST /api/v1/video-studio/tools/video-remix/generate/`
## Error behavior
- `401` or `403`: stop and request a valid API key.
- `402`: report insufficient credits.
- `429` and `5xx`: retry with exponential backoff.
- `400`: show validation error from response and let user fix input.
## Polling defaults
- Image jobs: poll every 2 seconds, timeout after 5 minutes.
- Video jobs: poll every 3 seconds, timeout after 10 minutes.
## Reusable clients in this skill
Use these files when code generation is requested:
- Python: `references/ulazai_client.py`
- JavaScript: `references/ulazai_client.js`
## Minimal cURL examples
```bash
curl -X GET https://ulazai.com/api/v1/models/image/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY"
```
```bash
curl -X POST https://ulazai.com/api/v1/generate/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Premium product shot with cinematic lighting",
"model": "nano_banana_2",
"size": "16:9",
"quality": "2K"
}'
```
```bash
curl -X POST https://ulazai.com/api/v1/video-studio/generate/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model_slug": "wan_2_6",
"prompt": "Cinematic product teaser, smooth camera movement",
"aspect_ratio": "16:9",
"duration_seconds": 10,
"quality_mode": "1080p"
}'
```
Python client
"""UlazAI point-and-shoot API client.
Minimal wrapper for:
- model discovery
- image generation/status/history
- video studio generation/status/history
- video studio tool endpoints
"""
from __future__ import annotations
import time
from typing import Any, Dict, Optional
import requests
class UlazAIAPIError(Exception):
"""Raised when the UlazAI API returns a non-success response."""
def __init__(
self,
*,
status_code: int,
message: str,
payload: Optional[Dict[str, Any]] = None,
) -> None:
super().__init__(f"HTTP {status_code}: {message}")
self.status_code = status_code
self.message = message
self.payload = payload or {}
class UlazAIClient:
def __init__(
self,
api_key: str,
*,
base_url: str = "https://ulazai.com",
timeout_seconds: int = 120,
session: Optional[requests.Session] = None,
) -> None:
if not api_key or not api_key.strip():
raise ValueError("api_key is required")
self.base_url = base_url.rstrip("/")
self.timeout_seconds = timeout_seconds
self.session = session or requests.Session()
self.session.headers.update(
{
"Authorization": f"Bearer {api_key.strip()}",
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "UlazAI-Python-Client/1.0",
}
)
def _request(
self,
method: str,
path: str,
*,
params: Optional[Dict[str, Any]] = None,
json_body: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
url = f"{self.base_url}{path}"
response = self.session.request(
method=method,
url=url,
params=params,
json=json_body,
timeout=self.timeout_seconds,
)
try:
payload = response.json()
except ValueError:
payload = {"success": False, "error": response.text or "Invalid JSON response"}
if response.ok:
return payload
error_message = (
str(payload.get("error") or payload.get("message") or response.reason)
if isinstance(payload, dict)
else response.reason
)
raise UlazAIAPIError(
status_code=response.status_code,
message=error_message,
payload=payload if isinstance(payload, dict) else None,
)
# Model discovery
def list_image_models(self) -> Dict[str, Any]:
return self._request("GET", "/api/v1/models/image/")
def list_video_models(self) -> Dict[str, Any]:
return self._request("GET", "/api/v1/models/video/")
# Images
def generate_image(
self,
*,
prompt: str,
model: str,
size: Optional[str] = None,
quality: Optional[str] = None,
google_search: Optional[bool] = None,
extra: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
payload: Dict[str, Any] = {"prompt": prompt, "model": model}
if size:
payload["size"] = size
if quality:
payload["quality"] = quality
if google_search is not None:
payload.setdefault("input", {})["google_search"] = bool(google_search)
if extra:
payload.update(extra)
return self._request("POST", "/api/v1/generate/", json_body=payload)
def get_image_status(self, generation_id: str) -> Dict[str, Any]:
return self._request("GET", f"/api/v1/generate/{generation_id}/")
def list_image_history(self, *, page: int = 1, limit: int = 20) -> Dict[str, Any]:
return self._request("GET", "/api/v1/generate/history/", params={"page": page, "limit": limit})
def wait_for_image(
self,
generation_id: str,
*,
timeout_seconds: int = 300,
poll_interval_seconds: float = 2.0,
) -> Dict[str, Any]:
deadline = time.time() + timeout_seconds
while time.time() < deadline:
payload = self.get_image_status(generation_id)
status_value = str(
payload.get("status")
or payload.get("data", {}).get("status")
or payload.get("generation", {}).get("status")
or ""
).lower()
if status_value in {"completed", "failed"}:
return payload
time.sleep(poll_interval_seconds)
raise TimeoutError(f"Image generation timed out after {timeout_seconds}s")
# Videos
def generate_video(
self,
*,
model_slug: str,
prompt: str,
aspect_ratio: Optional[str] = None,
duration_seconds: Optional[int] = None,
quality_mode: Optional[str] = None,
extra: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
payload: Dict[str, Any] = {"model_slug": model_slug, "prompt": prompt}
if aspect_ratio:
payload["aspect_ratio"] = aspect_ratio
if duration_seconds:
payload["duration_seconds"] = duration_seconds
if quality_mode:
payload["quality_mode"] = quality_mode
if extra:
payload.update(extra)
return self._request("POST", "/api/v1/video-studio/generate/", json_body=payload)
def get_video_status(self, job_id: str) -> Dict[str, Any]:
return self._request("GET", f"/api/v1/video-studio/status/{job_id}/")
def list_video_history(self, *, limit: int = 20) -> Dict[str, Any]:
return self._request("GET", "/api/v1/video-studio/history/", params={"limit": limit})
def wait_for_video(
self,
job_id: str,
*,
timeout_seconds: int = 600,
poll_interval_seconds: float = 3.0,
) -> Dict[str, Any]:
deadline = time.time() + timeout_seconds
while time.time() < deadline:
payload = self.get_video_status(job_id)
job = payload.get("job", {}) if isinstance(payload, dict) else {}
status_value = str(job.get("status") or payload.get("status") or "").lower()
if status_value in {"completed", "failed"}:
return payload
time.sleep(poll_interval_seconds)
raise TimeoutError(f"Video generation timed out after {timeout_seconds}s")
# Video tools
def generate_street_interview(self, payload: Dict[str, Any]) -> Dict[str, Any]:
return self._request(
"POST",
"/api/v1/video-studio/tools/street-interview/generate/",
json_body=payload,
)
def generate_ugc_ad_quick(self, payload: Dict[str, Any]) -> Dict[str, Any]:
return self._request(
"POST",
"/api/v1/video-studio/tools/ugc-ad-quick/generate/",
json_body=payload,
)
def generate_video_remix(self, payload: Dict[str, Any]) -> Dict[str, Any]:
return self._request(
"POST",
"/api/v1/video-studio/tools/video-remix/generate/",
json_body=payload,
)
The 60-second flow
| Step | Endpoint | Purpose |
|---|---|---|
| 1 | /api/v1/models/image/ / /api/v1/models/video/ |
Discover valid model and model_slug values |
| 2 | /api/v1/generate/ or /api/v1/video-studio/generate/ |
Create image or video generation job |
| 3 | /api/v1/generate/{generation_id}/ or /api/v1/video-studio/status/{job_id}/ |
Poll until completed and read result URLs |
Minimal cURL recipes
Discover image models
curl -X GET https://ulazai.com/api/v1/models/image/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY"
Generate image (unified endpoint)
curl -X POST https://ulazai.com/api/v1/generate/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Premium running shoe hero shot, dramatic studio light",
"model": "nano_banana_2",
"size": "16:9",
"quality": "2K"
}'
Generate video (all video models)
curl -X POST https://ulazai.com/api/v1/video-studio/generate/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model_slug": "wan_2_6",
"prompt": "Cinematic product teaser with smooth dolly motion",
"aspect_ratio": "16:9",
"duration_seconds": 10,
"quality_mode": "1080p"
}'
Poll video status
curl -X GET https://ulazai.com/api/v1/video-studio/status/YOUR_JOB_ID/ \
-H "Authorization: Bearer YOUR_ULAZAI_KEY"
Copy-paste agent skill
Paste this block in your own agent system prompt or skill config.
# UlazAI Point-and-Shoot Skill
Base URL: https://ulazai.com
Auth: Authorization: Bearer
1) Model discovery
- Images: GET /api/v1/models/image/
- Videos: GET /api/v1/models/video/
2) Image tasks
- Create: POST /api/v1/generate/
- Poll: GET /api/v1/generate/{generation_id}/
- History: GET /api/v1/generate/history/
3) Video tasks (all video model_slugs)
- Create: POST /api/v1/video-studio/generate/
- Poll: GET /api/v1/video-studio/status/{job_id}/
- History: GET /api/v1/video-studio/history/
4) Tool flows (video)
- Street interview: POST /api/v1/video-studio/tools/street-interview/generate/
- UGC ad quick: POST /api/v1/video-studio/tools/ugc-ad-quick/generate/
- Video remix: POST /api/v1/video-studio/tools/video-remix/generate/
5) Guardrails
- Always discover models first and validate requested slug.
- Retry 429/5xx with backoff.
- Stop immediately on 401/403 and request a valid key.
- On 402, report insufficient credits clearly.
Recommended architecture for white-label apps
- Keep UlazAI keys server-side only. Never expose raw keys in public frontend code.
- Create one backend adapter with a single request format for your own app.
- Map your internal model names to UlazAI model identifiers from discovery endpoints.
- Persist
generation_idandjob_idso polling and retries are idempotent. - Cache model capability responses for 5-15 minutes to reduce metadata traffic.