Control API reference
The local HTTP API that SimpliGen exposes on your machine, for building custom integrations and running generations headless.
SimpliGen runs a small HTTP Control API on your machine while the app is open. It is the same API the MCP server talks to, exposed directly so you can drive SimpliGen from your own scripts, services, or agents, including with the app minimized to the tray.
MCP vs the Control API
If you just want an AI agent (Claude, Codex, etc.) to use SimpliGen, set up the MCP server instead, it wraps this API. Use the Control API directly when you are writing a custom integration in any language.
Base URL and discovery
The server binds to loopback only and defaults to:
http://127.0.0.1:48199If that port is taken, SimpliGen falls back to an OS-assigned port. The current port (and API version) is always written to a discovery file in the app's user data directory:
# Windows
%APPDATA%\simpligen\agent-api.json
# macOS
~/Library/Application Support/simpligen/agent-api.json
# Linux
~/.config/simpligen/agent-api.json{ "port": 48199, "version": 1 }Read this file to find the live port rather than hardcoding 48199.
Requirements
The Control API only runs when the app is open and the machine is entitled to
agent access (it is part of your license). Requests are only served when the
Host header is a loopback alias; anything else gets 403 forbidden_host (a
DNS-rebinding guard).
Authentication
Every endpoint except GET /health requires a pairing token in the
Authorization header:
Authorization: Bearer sg-agent-xxxxxxxx...Create a token in the app under Settings → Connect an agent (see Connect an agent). The raw token is shown once; only a hash is stored. Each token can carry an optional spend cap for cloud generations. Revoking a token in the app takes effect immediately.
A missing or invalid token returns 401 unauthorized.
Conventions
- Requests: JSON bodies (
Content-Type: application/json), up to 50 MB. - Responses: always JSON.
- Errors: non-2xx responses have the shape
{ "errorCode": "...", "message": "..." }. - IDs: presets are addressed by
presetId(+ optionalpackId); jobs byjobId.
A minimal authenticated call:
TOKEN="sg-agent-..."
curl -s http://127.0.0.1:48199/status -H "Authorization: Bearer $TOKEN"Health
GET /health
Liveness probe. No authentication.
{ "ok": true, "name": "simpligen", "version": 1 }Capabilities
GET /capabilities
Lists every preset you can generate with, one entry per (preset, mediaType),
with its inputs, options, local readiness, and cloud eligibility/cost.
{
"capabilities": [
{
"packId": "local-core-pack",
"presetId": "z-image-turbo",
"mediaType": "image",
"name": "Z-Image Turbo",
"description": "Ultra-fast, high-quality images in seconds",
"apiProvider": null,
"inputs": {
"acceptsPrompt": true,
"requiresImage": false,
"acceptsReferenceImages": null,
"acceptsVideo": false,
"supportsLoras": true
},
"options": {
"steps": null,
"cfg": null,
"duration": null,
"resolution": null,
"aspectRatio": ["1:1", "3:4", "4:3", "9:16", "16:9"]
},
"cloudAliasTo": null,
"localReady": true,
"cloudEligible": true,
"cloudCost": 4
}
]
}| Field | Meaning |
|---|---|
localReady | All model files are installed; the preset can run locally now. |
cloudEligible | The preset can run on SimpliGen Cloud. |
cloudCost | Approximate credit cost for a cloud run (null if not eligible). |
apiProvider | Non-null for external-API presets (e.g. OpenAI, Google) which the Control API cannot dispatch. |
inputs / options | What the preset accepts and the option ranges/aspect ratios you may pass to POST /generate. steps/cfg are non-null only for presets that expose advanced controls. |
Status
GET /status
{
"engineRunning": true,
"engineVersion": "v0.26.0",
"cloudConnected": true,
"creditBalance": 1280,
"activeProject": { "id": "default", "name": "My Project" },
"characters": { "enabled": true, "base": 1, "edit": 1, "i2v": 1, "enhancePresets": 2 }
}Projects
Projects are the containers that hold generations.
GET /projects
{ "projects": [ { "id": "default", "name": "My Project", "slug": "my-project" } ] }POST /projects
{ "name": "Campaign assets" }Returns 201 with { "project": { ... } }. name is required (400 invalid_options otherwise).
Uploads
Inputs (source images, reference images, source videos) are never passed as
local paths. Upload the bytes first and pass the returned handle to
POST /generate.
POST /uploads
{ "filename": "ref.png", "dataBase64": "<base64 of the file bytes>" }{ "handle": "a1b2c3d4.png", "sizeBytes": 51234 }Max upload is 100 MB (413 asset_too_large). Unsupported extensions return
400 unsupported_file_type.
Generate
POST /generate
Enqueues a generation and returns immediately with a jobId. Poll
GET /jobs/:id (see Jobs) for the result.
{
"presetId": "z-image-turbo",
"packId": "local-core-pack",
"mediaType": "image",
"prompt": "a red fox in the snow, cinematic",
"backend": "auto",
"project": "default",
"image": "a1b2c3d4.png",
"referenceImages": [{ "handle": "e5f6.png", "label": "style" }],
"loras": [
{ "name": "RoomLora", "weight": 0.85 },
{ "name": "Mia", "weight": 0.75 }
],
"options": { "aspectRatio": "16:9", "steps": 8, "negativePrompt": "blurry, watermark" }
}| Field | Required | Notes |
|---|---|---|
presetId | yes | From GET /capabilities. |
mediaType | yes | "image" or "video". |
packId | no | If given, must match the preset's pack. |
prompt | conditional | Required when the preset needs a prompt. |
backend | no | "auto" (default), "local", or "cloud". Auto prefers local if installed, else cloud if connected. |
image | conditional | Upload handle. Required for image-to-X presets (inputs.requiresImage). |
video | conditional | Upload handle, for video-input presets. |
referenceImages | conditional | Array of handles or { handle, label }, for reference-image presets. |
loras | no | Array of { name, weight } to apply, for presets where inputs.supportsLoras is true. name matches a LoRA's display name (or filename) from GET /loras; weight defaults to the LoRA's default strength. Unknown names return 400 unknown_lora. Local backend only for now. |
project | no | Project id or slug. Defaults to the active project. |
options.aspectRatio | no | Must be one of the preset's supported ratios. |
options.steps / options.cfg | no | Within the preset's advertised ranges. |
options.negativePrompt | no | Negative prompt (the UI's Advanced Options negative). Also accepted at the top level as negativePrompt. |
options.durationSeconds | no | Video duration. |
options.resolution | no | Resolution tier label. |
{ "jobId": "gen_abc123", "backend": "local", "status": "queued" }For a cloud job the response also includes "cloudCost": <credits>.
Backends
External-API presets (where apiProvider is non-null, e.g. GPT Image 2, Veo)
cannot be dispatched here, you will get 400 unsupported_backend. Use a local
or SimpliGen Cloud preset. Cloud requires a connected account
(400 cloud_not_connected); local requires the models installed
(400 preset_not_installed, prepare them first).
GET /loras
Lists the user's installed LoRAs so agents can discover what to pass in
POST /generate's loras array. Match on name (the display name).
{
"loras": [
{ "name": "RoomLora", "filename": "RoomLora_000002400.safetensors", "baseModel": "flux", "triggerWords": "a room", "defaultStrength": 0.9 },
{ "name": "Mia", "filename": "mia_v2.safetensors", "baseModel": "flux", "triggerWords": "mia", "defaultStrength": 1.0 }
]
}Pass the LoRA where it fits the preset's base model (baseModel), and use its
own trigger words in your prompt so it activates.
Jobs
GET /jobs/:id
{
"job": {
"jobId": "gen_abc123",
"status": "completed",
"backend": "local",
"mediaType": "image",
"presetId": "z-image-turbo",
"projectId": "default",
"resultUrl": "local-file:///C:/Users/you/.../output.png",
"resultPath": "C:/Users/you/.../output.png",
"errorMessage": null,
"createdAt": "2026-06-25T20:00:00.000Z",
"completedAt": "2026-06-25T20:00:08.000Z"
}
}status is one of queued, processing, completed, failed. On success,
resultPath is the decoded local file path; on failure, errorMessage explains
why. Unknown id returns 404 job_not_found.
GET /jobs
Returns the 25 most recent jobs as { "jobs": [ ...job... ] }.
POST /jobs/:id/cancel
Cancels a queued/running job. Returns { "jobId", "status", "note?" }. A cloud
job already in flight may still complete on the server (noted in note).
Preparing presets
Local presets download their model files on first use. You can prepare them ahead of time.
POST /presets/prepare
{ "presetId": "z-image-turbo", "mediaType": "image" }Returns 200 { "status": "already_installed" } or 202 { "status": "started" }
(download runs in the background).
GET /presets/prepare
Returns in-flight downloads: { "downloads": [ ... ] }. Poll this, or
GET /capabilities (watch localReady), until the preset is ready.
Characters
Character Studio is scriptable too. Characters are local-only (cloud is rejected). See Characters for the concepts.
| Endpoint | Purpose |
|---|---|
GET /characters | List characters. |
GET /characters/:id | Get one character. |
GET /character-presets | Installed character presets (base / edit / i2v). |
GET /character-features | Available identity/appearance feature options. |
POST /characters | Create a character (mode: "generate" from a description, or "upload" from an uploaded base image handle). |
POST /characters/:id/generate-image | Generate an image of the character ({ prompt, aspect?, count?, presetId? }). |
POST /characters/:id/generate-frame | Generate a single video start frame. |
POST /characters/:id/animate | Animate the character into a video. |
POST /characters/:id/preset | Set the character's default preset. |
POST /characters/:id/enhance | Upscale/enhance a character asset. |
All generation endpoints return a jobId you poll via GET /jobs/:id, exactly
like POST /generate.
Recipes
Author multi-step recipes (flows) out of packs and presets. Discover the building blocks, compose from a high-level spec, then optionally publish.
| Endpoint | Purpose |
|---|---|
GET /recipe-blocks | Entry points + every preset usable as a step, with its port contract. |
POST /recipes/validate | Dry-run a spec (compose+validate) or a config, no save. Returns validation. |
POST /recipes | Compose from a spec and save a draft. Returns { recipeId, valid, errors, config }. |
GET /recipes | List saved recipe drafts. |
GET /recipes/:id | Get one saved recipe and its full config. |
POST /recipes/:id/publish | Submit to the Creator Marketplace (needs a connected account). |
A spec is a high-level description; the app auto-wires ports, the step-to-step
media chain, and a prompt input per step, then validates.
{
"spec": {
"name": "Product to spinning video",
"category": "product",
"steps": [
{ "preset": "krea2-pack:krea2-turbo" },
{ "preset": "wan-2.2-video-pack:wan-2.2-i2v" }
]
}
}{
"recipeId": "b1f2…",
"valid": true,
"errors": [],
"config": { "blocks": [ "…" ], "inputs": [ "…" ], "output": { "from": "n2.video" } }
}Composing and saving needs no creator account. POST /recipes/:id/publish
requires a connected account and returns not_connected (with needsConnect: true)
until the user connects one in the app; the same draft then publishes unchanged.
Every preset the recipe uses must belong to a distributable first-party pack.
Spend caps
When a token has a spend cap, cloud generations are priced before dispatch and
rejected if they would exceed the remaining allowance
(402 exceeds_spend_cap). If the cost can't be determined (cloud status
unavailable), a capped token fails closed with 503 cost_unknown. Local
generations are always free of credit cost.
A complete flow
BASE=http://127.0.0.1:48199
AUTH="Authorization: Bearer sg-agent-..."
# 1. Pick a ready preset
curl -s $BASE/capabilities -H "$AUTH"
# 2. Generate
JOB=$(curl -s $BASE/generate -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"presetId":"z-image-turbo","mediaType":"image","prompt":"a red fox in the snow","options":{"aspectRatio":"16:9"}}' \
| jq -r .jobId)
# 3. Poll until done, then read resultPath
curl -s $BASE/jobs/$JOB -H "$AUTH" | jq '.job | {status, resultPath, errorMessage}'Error codes
| HTTP | errorCode | When |
|---|---|---|
| 400 | invalid_json | Body is not valid JSON. |
| 400 | invalid_options | A required field is missing or malformed. |
| 400 | invalid_preset | Preset/pack/mediaType mismatch. |
| 400 | needs_* | A required input (prompt, image, references) is missing. |
| 400 | invalid_handle | An input was not a valid upload handle. |
| 400 | unsupported_backend | Preset runs on an external API. |
| 400 | preset_not_installed | Local backend chosen but models aren't installed. |
| 400 | cloud_not_connected | Cloud backend chosen but no account is connected. |
| 401 | unauthorized | Missing or invalid token. |
| 402 | exceeds_spend_cap | Cloud cost exceeds the token's spend cap. |
| 403 | forbidden_host | Non-loopback Host header. |
| 404 | not_found / invalid_preset / job_not_found | Unknown route, preset, or job. |
| 413 | asset_too_large | Upload over 100 MB. |
| 503 | cost_unknown | Capped token; cloud cost couldn't be determined. |
| 500 | internal_error | Unexpected server error. |