Developer Documentation

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

1

Create an API key

Settings → API → Create Key

2

Buy API credits

Separate from dashboard credits

3

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.

Upload images
Pick a voice
Submit prompt
AI plans scenes
Image editing
Video generation
Voiceover
Final compositing

Quick Start

Generate a video in 4 API calls:

Step 1 — Check your balance

bash
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

bash
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

bash
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

bash
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.

bash
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>"
python
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())
typescript
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

1

Go to Settings → API

Click "Create Key" and name it (e.g. "Production", "Staging")

2

Copy the key immediately

The full key is shown only once. We store a SHA-256 hash — the plain key cannot be recovered.

3

Store as an environment variable

ARS0N_API_KEY=sk_live_... — never hardcode keys in source.

Key Properties

Formatsk_live_ + 64 hex chars
ScopeOrganization-level — all members share keys
LimitUnlimited keys per org (use one per environment)
RevokeInstant — 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

POST/api/v1/generateDeducts API credits

Start a video generation pipeline. Provide a prompt, product images, a voice, and optional style settings.

Request Body

FieldTypeReqDescription
promptstringRequiredCreative prompt describing your ad (1-2000 chars)
imageUrlsstring[]*Product image URLs (1-10). Server downloads them.
imageAssetIdsstring[]*Pre-uploaded asset IDs (1-10) from /v1/assets
durationnumberRequiredVideo length: 15, 30, or 60 seconds
voiceIdstringRequiredVoice ID from /v1/voices
aspectRatiostringOptional"9:16" (default) or "16:9"
veoModelstringOptionalVideo quality tier (see quality options in dashboard)
logoAssetIdstringOptionalLogo overlay — upload via /v1/assets first
musicAssetIdstringOptionalBackground music — upload MP3/WAV via /v1/assets
companyNamestringOptionalCompany name shown in CTA end card (max 100)
ctaDesignstringOptional"minimal" | "bold" | "gradient" | "split" | "glass" | "neon" | "editorial" | "brutalist"
gradientColorstringOptionalHex color for gradient CTA (#FF5500)
voiceStylestringOptionalHint for AI voice direction (max 200)
musicMoodstringOptionalHint for AI music selection (max 200)
tonestringOptionalCreative tone — "luxury", "playful", "corporate" (max 200)
editStylestringOptionalImage 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"
emotionstringOptionalAnimation emotion style (max 200)
qualityLoopVariants1 | 2 | 3OptionalImage edit variants per scene. Default 2. Use 3 for max quality.

* Either imageUrls or imageAssetIds is required (not both).

Response

json
{
  "projectId": "proj_abc123",
  "versionId": "ver_def456",   // Use this to poll status
  "totalCredits": 115,
  "jobCount": 6
}
GET/api/v1/jobs/{versionId}

Poll generation progress. Call every 10 seconds until status is "completed" or "failed".

json
{
  "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" }
    ]
  }
}
GET/api/v1/credits

Check your API credit balance, current tier, and active job count.

json
{
  "balance": 450,
  "tier": "starter",
  "activeJobs": 1,
  "limits": {
    "maxConcurrentJobs": 3,
    "rateLimitRpm": 60
  }
}
POST/api/v1/assets

Upload 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

  • file required — The file to upload
  • category optional "product_image" | "logo" | "music" (inferred from MIME type if omitted)
json
{
  "assetId": "asset_abc123",    // Use in imageAssetIds, logoAssetId, etc.
  "type": "image",              // "image" or "audio"
  "publicUrl": "https://..."
}
GET/api/v1/voices

Browse 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
json
{
  "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"
    }
  ]
}
GET/api/v1/projects

List your API-created projects with their latest version status. Paginated.

Query Parameters

  • limit — 1-100 (default 20)
  • offset — default 0
GET/api/v1/packages

List available API credit packages and their prices. Use the id to purchase in the dashboard.

json
{
  "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

bash
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.

StepCreditsPer
Image editing10Per variant (2/scene default)
Video generation35Per scene clip
Voiceover3Per video
Compositing2Per video

Example: A 15-second video with 3 scenes costs roughly 170 credits (60 editing + 105 video + 3 voiceover + 2 compositing).

List Packages

bash
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

TierRequests/minConcurrent JobsBest For
Free301Testing & prototyping
Starter603Small apps & MVPs
Pro12010Production workloads
Enterprise30050High-volume pipelines

Response Headers

Every API response includes rate limit headers. Use them to throttle your client and avoid hitting limits.

bash
# 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 debugging

Handling 429 — Rate Limited

Two scenarios return 429:

Too many requests

Wait until X-RateLimit-Reset before retrying.

json
{ "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.

json
{
  "error": "Concurrency limit reached. Wait for active jobs to complete.",
  "active": 3,
  "max": 3
}

Python retry with backoff

python
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.

json
{
  "error": "Insufficient credits: 100 required but only 45 available",
  "details": { ... }  // Present on validation errors (400)
}

Status Codes

CodeWhenFix
200SuccessProcess the response
201Resource created (asset upload)Use the returned assetId
400Invalid request body or paramsCheck details for field errors
401Missing, invalid, or revoked keyCheck your Authorization header
403Asset not owned by your orgVerify the asset ID belongs to you
404Resource not foundCheck the version / project ID
429Rate or concurrency limit hitWait for X-RateLimit-Reset
500Server errorRetry with backoff. Include X-Request-Id in support requests.

Validation Errors (400)

When request validation fails, the details field contains field-level errors:

json
{
  "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.

python
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.

python
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.

typescript
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).

bash
# 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

bash
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

bash
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

bash
# 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", ... }] }