ars0n API
Generate professional video ads programmatically. Upload images, pick a voice, set your style — get a finished video back. Same engine as the dashboard, fully accessible via REST.
Getting Started
Create an API key
Settings → API → Create Key
Buy API credits
Separate from dashboard credits
Make your first call
POST to /api/v1/generate
The Pipeline
Every video goes through this flow. The API handles it all — you just submit and poll.
Quick Start
Generate a video in 4 API calls:
Step 1 — Check your balance
curl https://your-app.com/api/v1/credits \
-H "Authorization: Bearer $ARS0N_API_KEY"
# → { "balance": 500, "tier": "starter", "activeJobs": 0 }Step 2 — Find a voice
curl "https://your-app.com/api/v1/voices?gender=male&accent=american" \
-H "Authorization: Bearer $ARS0N_API_KEY"
# → { "voices": [{ "voice_id": "pNInz6obpgDQGcFmaJgB", "name": "Adam", ... }] }Step 3 — Generate
curl -X POST https://your-app.com/api/v1/generate \
-H "Authorization: Bearer $ARS0N_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Premium wireless headphones — sleek, modern, lifestyle",
"imageUrls": ["https://example.com/headphones.png"],
"duration": 15,
"voiceId": "pNInz6obpgDQGcFmaJgB",
"aspectRatio": "9:16"
}'
# → { "versionId": "abc-123", "totalCredits": 100, "jobCount": 5 }Step 4 — Poll until done
curl https://your-app.com/api/v1/jobs/abc-123 \
-H "Authorization: Bearer $ARS0N_API_KEY"
# → { "status": "completed", "progress": 100, "videoUrl": "/api/video?id=..." }Base URL: All endpoints are prefixed with /api/v1. API credits are billed separately from dashboard credits — your platform balance is never touched.
Authentication
Every request requires a Bearer token. Keys use the sk_live_ prefix and are scoped to your organization.
curl https://your-app.com/api/v1/credits \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..."
# Every language works the same way:
# Just set the Authorization header to "Bearer <your-key>"import os, requests
API_KEY = os.environ["ARS0N_API_KEY"]
resp = requests.get(
"https://your-app.com/api/v1/credits",
headers={"Authorization": f"Bearer {API_KEY}"},
)
print(resp.json())const API_KEY = process.env.ARS0N_API_KEY!;
const res = await fetch(
"https://your-app.com/api/v1/credits",
{
headers: {
Authorization: `Bearer ${API_KEY}`,
},
}
);
console.log(await res.json());Creating Keys
Go to Settings → API
Click "Create Key" and name it (e.g. "Production", "Staging")
Copy the key immediately
The full key is shown only once. We store a SHA-256 hash — the plain key cannot be recovered.
Store as an environment variable
ARS0N_API_KEY=sk_live_... — never hardcode keys in source.
Key Properties
| Format | sk_live_ + 64 hex chars |
| Scope | Organization-level — all members share keys |
| Limit | Unlimited keys per org (use one per environment) |
| Revoke | Instant — revoked keys return 401 immediately |
Security
- Never commit keys to git or include in client-side code
- Rotate periodically — create new, deploy, revoke old
- Use separate keys for dev / staging / production
- Monitor "last used" in the dashboard, revoke idle keys
Endpoints
All endpoints require Bearer authentication. Base path: /api/v1
/api/v1/generateDeducts API creditsStart a video generation pipeline. Provide a prompt, product images, a voice, and optional style settings.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| prompt | string | Required | Creative prompt describing your ad (1-2000 chars) |
| imageUrls | string[] | * | Product image URLs (1-10). Server downloads them. |
| imageAssetIds | string[] | * | Pre-uploaded asset IDs (1-10) from /v1/assets |
| duration | number | Required | Video length: 15, 30, or 60 seconds |
| voiceId | string | Required | Voice ID from /v1/voices |
| aspectRatio | string | Optional | "9:16" (default) or "16:9" |
| veoModel | string | Optional | Video quality tier (see quality options in dashboard) |
| logoAssetId | string | Optional | Logo overlay — upload via /v1/assets first |
| musicAssetId | string | Optional | Background music — upload MP3/WAV via /v1/assets |
| companyName | string | Optional | Company name shown in CTA end card (max 100) |
| ctaDesign | string | Optional | "minimal" | "bold" | "gradient" | "split" | "glass" | "neon" | "editorial" | "brutalist" |
| gradientColor | string | Optional | Hex color for gradient CTA (#FF5500) |
| voiceStyle | string | Optional | Hint for AI voice direction (max 200) |
| musicMood | string | Optional | Hint for AI music selection (max 200) |
| tone | string | Optional | Creative tone — "luxury", "playful", "corporate" (max 200) |
| editStyle | string | Optional | Image editing direction applied to every scene (max 500). Guides both the AI scene planner and image editor. E.g. "place all screenshots on realistic iPhone screens held by people in lifestyle settings" |
| emotion | string | Optional | Animation emotion style (max 200) |
| qualityLoopVariants | 1 | 2 | 3 | Optional | Image edit variants per scene. Default 2. Use 3 for max quality. |
* Either imageUrls or imageAssetIds is required (not both).
Response
{
"projectId": "proj_abc123",
"versionId": "ver_def456", // Use this to poll status
"totalCredits": 115,
"jobCount": 6
}/api/v1/jobs/{versionId}Poll generation progress. Call every 10 seconds until status is "completed" or "failed".
{
"versionId": "ver_def456",
"status": "generating", // "generating" | "completed" | "failed"
"progress": 60, // 0-100
"videoUrl": null, // Set when status="completed"
"totalCredits": 115,
"jobs": {
"total": 6,
"completed": 3,
"failed": 0,
"details": [
{ "id": "...", "job_type": "nanobanana_edit", "status": "completed" },
{ "id": "...", "job_type": "video_generation", "status": "processing" }
]
}
}/api/v1/creditsCheck your API credit balance, current tier, and active job count.
{
"balance": 450,
"tier": "starter",
"activeJobs": 1,
"limits": {
"maxConcurrentJobs": 3,
"rateLimitRpm": 60
}
}/api/v1/assetsUpload images or audio as multipart form data. Use the returned assetId in generate calls.
Images
PNG, JPEG, WebP, SVG — max 10MB
Audio
MP3, WAV, OGG, AAC, M4A — max 15MB
Form Fields
filerequired — The file to uploadcategoryoptional — "product_image" | "logo" | "music" (inferred from MIME type if omitted)
{
"assetId": "asset_abc123", // Use in imageAssetIds, logoAssetId, etc.
"type": "image", // "image" or "audio"
"publicUrl": "https://..."
}/api/v1/voicesBrowse the voice library. Use the voice_id from the response as your voiceId in generate calls.
Query Parameters (all optional)
gender— "male", "female"accent— "american", "british", "australian", etc.age— "young", "middle_aged", "old"q— Free-text search across name and description
{
"voices": [
{
"voice_id": "pNInz6obpgDQGcFmaJgB",
"name": "Adam",
"gender": "male",
"accent": "american",
"age": "middle_aged",
"description": "Deep, warm, authoritative voice",
"preview_url": "https://...",
"use_case": "narration",
"category": "premade"
}
]
}/api/v1/projectsList your API-created projects with their latest version status. Paginated.
Query Parameters
limit— 1-100 (default 20)offset— default 0
/api/v1/packagesList available API credit packages and their prices. Use the id to purchase in the dashboard.
{
"packages": [
{ "id": "...", "name": "API Starter", "credits": 500, "priceCents": 3900, "priceDisplay": "$39", "popular": false },
{ "id": "...", "name": "API Growth", "credits": 2000, "priceCents": 11900, "priceDisplay": "$119", "popular": true },
{ "id": "...", "name": "API Scale", "credits": 5000, "priceCents": 24900, "priceDisplay": "$249", "popular": false }
]
}Credits & Pricing
API calls consume API credits — a separate balance from your dashboard credits. Purchase packages in Settings or view them at /pricing.
Check Your Balance
curl https://your-app.com/api/v1/credits \
-H "Authorization: Bearer $ARS0N_API_KEY"
# → {
# "balance": 450,
# "tier": "starter",
# "activeJobs": 1,
# "limits": { "maxConcurrentJobs": 3, "rateLimitRpm": 60 }
# }Cost Per Operation
Credits are deducted upfront when you call /generate. The exact cost depends on scene count. The response tells you what was charged.
| Step | Credits | Per |
|---|---|---|
| Image editing | 10 | Per variant (2/scene default) |
| Video generation | 35 | Per scene clip |
| Voiceover | 3 | Per video |
| Compositing | 2 | Per video |
Example: A 15-second video with 3 scenes costs roughly 170 credits (60 editing + 105 video + 3 voiceover + 2 compositing).
List Packages
curl https://your-app.com/api/v1/packages \
-H "Authorization: Bearer $ARS0N_API_KEY"
# → { "packages": [
# { "name": "API Starter", "credits": 500, "priceCents": 3900 },
# { "name": "API Growth", "credits": 2000, "priceCents": 11900 },
# { "name": "API Scale", "credits": 5000, "priceCents": 24900 }
# ] }See full pricing details and plan comparison at /pricing →
Rate Limits & Concurrency
Two types of limits protect the system: rate limits (requests per minute) and concurrency limits (parallel video jobs). Both scale with your tier.
Tiers
| Tier | Requests/min | Concurrent Jobs | Best For |
|---|---|---|---|
| Free | 30 | 1 | Testing & prototyping |
| Starter | 60 | 3 | Small apps & MVPs |
| Pro | 120 | 10 | Production workloads |
| Enterprise | 300 | 50 | High-volume pipelines |
Response Headers
Every API response includes rate limit headers. Use them to throttle your client and avoid hitting limits.
# Headers included on every response:
X-RateLimit-Limit: 60 # Your max requests per window
X-RateLimit-Remaining: 42 # Requests left in this window
X-RateLimit-Reset: 1711234567 # Unix timestamp when window resets
X-Request-Id: a1b2c3d4-... # Unique ID for debuggingHandling 429 — Rate Limited
Two scenarios return 429:
Too many requests
Wait until X-RateLimit-Reset before retrying.
{ "error": "Rate limit exceeded. Try again later." }Too many concurrent jobs
Wait for an active job to finish. The response tells you the current count.
{
"error": "Concurrency limit reached. Wait for active jobs to complete.",
"active": 3,
"max": 3
}Python retry with backoff
import time
def api_call_with_retry(fn, max_retries=3):
for attempt in range(max_retries):
resp = fn()
if resp.status_code != 429:
return resp
reset_at = int(resp.headers.get("X-RateLimit-Reset", 0))
wait = max(reset_at - time.time(), 1)
print(f"Rate limited. Waiting {wait:.0f}s...")
time.sleep(wait)
raise Exception("Max retries exceeded")Errors
All errors follow the same shape. Use the HTTP status code for control flow and the error field for user-facing messages.
{
"error": "Insufficient credits: 100 required but only 45 available",
"details": { ... } // Present on validation errors (400)
}Status Codes
| Code | When | Fix |
|---|---|---|
| 200 | Success | Process the response |
| 201 | Resource created (asset upload) | Use the returned assetId |
| 400 | Invalid request body or params | Check details for field errors |
| 401 | Missing, invalid, or revoked key | Check your Authorization header |
| 403 | Asset not owned by your org | Verify the asset ID belongs to you |
| 404 | Resource not found | Check the version / project ID |
| 429 | Rate or concurrency limit hit | Wait for X-RateLimit-Reset |
| 500 | Server error | Retry with backoff. Include X-Request-Id in support requests. |
Validation Errors (400)
When request validation fails, the details field contains field-level errors:
{
"error": "Validation failed",
"details": {
"fieldErrors": {
"duration": ["Duration must be 15, 30, or 60"],
"voiceId": ["Required"]
},
"formErrors": []
}
}Request IDs
Every response includes X-Request-Id. Log it — when something goes wrong, include it in support tickets for instant lookup.
resp = requests.post(f"{BASE_URL}/generate", headers=headers, json=payload)
if not resp.ok:
request_id = resp.headers.get("X-Request-Id")
print(f"Failed: {resp.status_code} — Request ID: {request_id}")
print(resp.json())Full Examples
Complete end-to-end workflows: upload assets, find a voice, generate a video, and download the result.
Python — Full Workflow
Upload a product image, pick a voice, generate a video with all customization options, and poll until complete.
import requests, time, os
API_KEY = os.environ["ARS0N_API_KEY"]
BASE = "https://your-app.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# ── 1. Upload product image ──────────────────────────────────
with open("product.png", "rb") as f:
resp = requests.post(
f"{BASE}/assets",
headers=HEADERS,
files={"file": ("product.png", f, "image/png")},
data={"category": "product_image"},
)
resp.raise_for_status()
image_id = resp.json()["assetId"]
print(f"Uploaded image: {image_id}")
# ── 2. Upload logo ───────────────────────────────────────────
with open("logo.png", "rb") as f:
resp = requests.post(
f"{BASE}/assets",
headers=HEADERS,
files={"file": ("logo.png", f, "image/png")},
data={"category": "logo"},
)
resp.raise_for_status()
logo_id = resp.json()["assetId"]
# ── 3. Upload background music ───────────────────────────────
with open("bg-music.mp3", "rb") as f:
resp = requests.post(
f"{BASE}/assets",
headers=HEADERS,
files={"file": ("bg-music.mp3", f, "audio/mpeg")},
data={"category": "music"},
)
resp.raise_for_status()
music_id = resp.json()["assetId"]
# ── 4. Find a voice ──────────────────────────────────────────
resp = requests.get(
f"{BASE}/voices",
headers=HEADERS,
params={"gender": "female", "accent": "american"},
)
resp.raise_for_status()
voices = resp.json()["voices"]
voice_id = voices[0]["voice_id"]
print(f"Using voice: {voices[0]['name']} ({voice_id})")
# ── 5. Generate video with full options ───────────────────────
resp = requests.post(f"{BASE}/generate", headers={
**HEADERS, "Content-Type": "application/json",
}, json={
# Required
"prompt": "Premium noise-canceling headphones. Sleek design, deep bass.",
"imageAssetIds": [image_id],
"duration": 30,
"voiceId": voice_id,
# Style
"aspectRatio": "9:16",
"ctaDesign": "gradient",
"gradientColor": "#FF5500",
"companyName": "SoundWave Audio",
"logoAssetId": logo_id,
"musicAssetId": music_id,
# Creative direction
"voiceStyle": "energetic and confident",
"musicMood": "upbeat electronic",
"tone": "premium luxury tech",
"emotion": "excitement",
"editStyle": "Product floating in mid-air with dramatic studio lighting, reflections on glossy surface, cinematic depth of field",
# Pipeline tuning
"qualityLoopVariants": 3, # Max quality — 3 variants per scene
})
resp.raise_for_status()
data = resp.json()
version_id = data["versionId"]
print(f"Generation started: {version_id}")
print(f"Credits charged: {data['totalCredits']}")
print(f"Jobs queued: {data['jobCount']}")
# ── 6. Poll for completion ────────────────────────────────────
while True:
resp = requests.get(f"{BASE}/jobs/{version_id}", headers=HEADERS)
resp.raise_for_status()
status = resp.json()
pct = status["progress"]
jobs = status["jobs"]
print(f" {status['status']} — {pct}% "
f"({jobs['completed']}/{jobs['total']} jobs)")
if status["status"] == "completed":
print(f"\nVideo ready: {status['videoUrl']}")
break
elif status["status"] == "failed":
print(f"\nFailed. Check job details:")
for j in jobs["details"]:
if j["status"] == "failed":
print(f" {j['job_type']}: {j['error_message']}")
break
time.sleep(10)TypeScript — Full Workflow
Same workflow in Node.js with proper error handling and typed responses.
import fs from "fs";
const API_KEY = process.env.ARS0N_API_KEY!;
const BASE = "https://your-app.com/api/v1";
const headers = { Authorization: `Bearer ${API_KEY}` };
// ── Helper: upload a file ────────────────────────────────────
async function uploadAsset(
filePath: string,
category: string,
): Promise<string> {
const file = fs.readFileSync(filePath);
const form = new FormData();
form.append("file", new Blob([file]), filePath.split("/").pop()!);
form.append("category", category);
const res = await fetch(`${BASE}/assets`, {
method: "POST",
headers,
body: form,
});
if (!res.ok) throw new Error(`Upload failed: ${await res.text()}`);
return (await res.json()).assetId;
}
// ── Helper: poll until done ──────────────────────────────────
async function pollUntilDone(versionId: string): Promise<{
status: string;
videoUrl: string | null;
}> {
while (true) {
const res = await fetch(`${BASE}/jobs/${versionId}`, { headers });
const data = await res.json();
console.log(` ${data.status} — ${data.progress}%`);
if (data.status === "completed" || data.status === "failed") {
return data;
}
await new Promise((r) => setTimeout(r, 10_000));
}
}
// ── Main ─────────────────────────────────────────────────────
async function main() {
// 1. Upload assets
const imageId = await uploadAsset("./product.png", "product_image");
const logoId = await uploadAsset("./logo.png", "logo");
const musicId = await uploadAsset("./music.mp3", "music");
console.log("Assets uploaded");
// 2. Find a voice
const voicesRes = await fetch(
`${BASE}/voices?gender=male&q=narrator`,
{ headers },
);
const { voices } = await voicesRes.json();
const voiceId = voices[0].voice_id;
console.log(`Using voice: ${voices[0].name}`);
// 3. Generate
const genRes = await fetch(`${BASE}/generate`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
prompt: "Premium headphones — immersive sound, modern design",
imageAssetIds: [imageId],
duration: 15,
voiceId,
aspectRatio: "9:16",
logoAssetId: logoId,
musicAssetId: musicId,
companyName: "SoundWave",
ctaDesign: "bold",
tone: "premium tech",
}),
});
if (!genRes.ok) throw new Error(await genRes.text());
const { versionId, totalCredits } = await genRes.json();
console.log(`Started (${totalCredits} credits)`);
// 4. Poll
const result = await pollUntilDone(versionId);
if (result.videoUrl) {
console.log(`Video: ${result.videoUrl}`);
}
}
main().catch(console.error);cURL — Quick Generate
Minimal example using image URLs (no pre-upload needed).
# Generate with image URLs — server downloads them for you
curl -X POST https://your-app.com/api/v1/generate \
-H "Authorization: Bearer $ARS0N_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Luxury skincare product — clean, minimal, elegant",
"imageUrls": [
"https://example.com/product-front.png",
"https://example.com/product-side.png"
],
"duration": 15,
"voiceId": "pNInz6obpgDQGcFmaJgB",
"aspectRatio": "16:9",
"companyName": "Glow Labs",
"ctaDesign": "minimal"
}'cURL — Upload Assets
Upload a product image
curl -X POST https://your-app.com/api/v1/assets \
-H "Authorization: Bearer $ARS0N_API_KEY" \
-F "file=@product-photo.png" \
-F "category=product_image"
# → { "assetId": "abc-123", "type": "image", "publicUrl": "https://..." }Upload background music
curl -X POST https://your-app.com/api/v1/assets \
-H "Authorization: Bearer $ARS0N_API_KEY" \
-F "file=@background.mp3" \
-F "category=music"
# → { "assetId": "def-456", "type": "audio", "publicUrl": "https://..." }cURL — Browse Voices
# List all voices
curl https://your-app.com/api/v1/voices \
-H "Authorization: Bearer $ARS0N_API_KEY"
# Filter by gender + accent
curl "https://your-app.com/api/v1/voices?gender=female&accent=british" \
-H "Authorization: Bearer $ARS0N_API_KEY"
# Search by name or description
curl "https://your-app.com/api/v1/voices?q=narrator" \
-H "Authorization: Bearer $ARS0N_API_KEY"
# Each voice has a voice_id — use it in the generate call:
# → { "voices": [{ "voice_id": "abc...", "name": "Charlotte", ... }] }