API reference

The Sula API lets you generate images and video from your own code, or from Claude, using an API key. It's a small JSON API over HTTPS.

Base URL: https://your-sula-host  (while self-hosted in dev this is http://127.0.0.1:4790).

Authentication

Create a key in the app under API keys. Send it as a Bearer token on every request. Keys are shown once, store them like a password; anyone with a key spends your credits.

Authorization: Bearer sula_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Revoke a key any time in the app; revoked keys immediately stop working.

Quickstart

Generate an image (returns synchronously):

curl -X POST $BASE/api/generate \
  -H "Authorization: Bearer $SULA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"flux-2-pro","prompt":"a matte black water bottle on beige, studio light"}'
{ "id":"a1b2…", "state":"done", "kind":"image",
  "file":"a1b2….jpg", "credits":{"image":14,"main":0} }

Download the result from $BASE/media/<file> (same Bearer key).

POST /api/generate

Create a generation. Images return state:"done" immediately; videos return state:"running" with an id you poll.

Body

FieldTypeNotes
modelstringRequired. See models.
promptstringRequired. For talking video, put the spoken line in "quotes".
aspect_ratiostringOptional. e.g. "9:16" (default), "16:9", "1:1".
enhanceboolOptional, images only, default true. Short prompts get expanded with photographic detail before generating; the response then carries enhanced: true and prompt_used. Pass false for literal prompts.

Python

import requests, os, time
BASE, KEY = os.environ["BASE"], os.environ["SULA_KEY"]
h = {"Authorization": f"Bearer {KEY}"}

r = requests.post(f"{BASE}/api/generate", headers=h,
    json={"model":"veo-3.1-fast",
          "prompt":'a woman holding a pink bottle, she says: "you need these"',
          "aspect_ratio":"9:16"}).json()

gid = r["id"]
while r.get("state") == "running":
    time.sleep(4)
    r = requests.get(f"{BASE}/api/status", headers=h, params={"id":gid}).json()

print("done:", f"{BASE}/media/{r['file']}")

Check the model is callable before you build against it. Any model whose Status is not live returns 503 and is not charged, so the example above will 503 while its provider key is missing.

POST /api/tool

Run an on-device edit tool on one of your generations (pass a generation id as source). Image tools return a new image synchronously; captionrm-video is a video tool that runs asynchronously, the response is {"state":"running","id":…}; poll /api/status?id=… until done.

opCreditsSourceDoes
upscale3image4× super-resolution
captionrm-video20videoErase burned-in subtitles from a clip, on-device LaMa inpaint, keeps the original audio. Async.
curl -X POST $BASE/api/tool \
  -H "Authorization: Bearer $SULA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"op":"upscale","source":"<image-id>"}'

GET /api/status?id=<id>

Poll a video job. Returns {"state":"running"}, then {"state":"done","file":"…mp4"}, or {"state":"failed"} (failed jobs are auto-refunded).

GET /api/models

Public, no key needed. Lists models, their credit cost, live status, and the plan tiers. This is the source of truth for pricing: the table below is built from it on every page load, and you should read it the same way rather than hardcoding costs.

Per model: credits is the cost of one request, or of one block when per_block_s is set. live is whether it is callable now, and status says why when it is not; video models report "live":false until the video provider is connected. Alongside the list, video_available reports whether video as a whole is currently accepting jobs, with video_reason when it is not.

History & media

GET /api/history, your last 100 generations.
GET /media/<file>, download a generation (auth required; you can only fetch your own files).

GET /api/me

Returns your email and credit balances: {"email":…, "credits":{"image":14,"main":0}}. The image pool is your free signup credits (images only); main is paid credits (any model).

Models & credits

Every row below is rendered from GET /api/models when this page loads, so it is whatever the API will charge you at the moment you read it. Nothing here is copied into the page by hand.

Rendering the table needs JavaScript. With JavaScript off, read the same list straight from the endpoint, which needs no API key:

curl -s $BASE/api/models

Each entry carries id, kind, credits, live and status. Treat credits from that response as the price, and live as whether you can call it right now.

Use Sula from Claude (MCP)

Sula speaks MCP two ways, so Claude can generate for you in plain English ("make me a talking ad for my serum in Jessica's voice") using your API key and your credits. The simplest is the built-in HTTP endpoint, no extra process:

claude mcp add --transport http sula http://127.0.0.1:4790/mcp \
  --header "Authorization: Bearer sula_your_key"

Setup for Cursor and other clients is on the MCP page. Prefer a local stdio server? That ships too:

Claude Code (stdio)

claude mcp add sula \
  -e SULA_API_KEY=sula_your_key \
  -- python3 /path/to/sula_mcp.py

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "sula": {
      "command": "python3",
      "args": ["/path/to/sula_mcp.py"],
      "env": { "SULA_API_KEY": "sula_your_key" }
    }
  }
}

Claude then gets four tools: sula_generate_image (FLUX or Nano Banana), sula_talking_ad (Veo person, your voice pick, 8–32s), sula_remove_captions (clean a local clip), and sula_credits. Results are saved to your Downloads folder.

Errors

CodeMeaning
401Missing/invalid/revoked key.
400Bad request (missing prompt or unknown model).
402Not enough credits. Response includes your current balances.
503Model temporarily unavailable (e.g. video before the provider is connected).

Get an API key →