SimpliGen Docs
AI Agents

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:48199

If 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
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 (+ optional packId); jobs by jobId.

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
    }
  ]
}
FieldMeaning
localReadyAll model files are installed; the preset can run locally now.
cloudEligibleThe preset can run on SimpliGen Cloud.
cloudCostApproximate credit cost for a cloud run (null if not eligible).
apiProviderNon-null for external-API presets (e.g. OpenAI, Google) which the Control API cannot dispatch.
inputs / optionsWhat 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

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

Request
{ "filename": "ref.png", "dataBase64": "<base64 of the file bytes>" }
Response (201)
{ "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.

Request
{
  "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" }
}
FieldRequiredNotes
presetIdyesFrom GET /capabilities.
mediaTypeyes"image" or "video".
packIdnoIf given, must match the preset's pack.
promptconditionalRequired when the preset needs a prompt.
backendno"auto" (default), "local", or "cloud". Auto prefers local if installed, else cloud if connected.
imageconditionalUpload handle. Required for image-to-X presets (inputs.requiresImage).
videoconditionalUpload handle, for video-input presets.
referenceImagesconditionalArray of handles or { handle, label }, for reference-image presets.
lorasnoArray 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.
projectnoProject id or slug. Defaults to the active project.
options.aspectRationoMust be one of the preset's supported ratios.
options.steps / options.cfgnoWithin the preset's advertised ranges.
options.negativePromptnoNegative prompt (the UI's Advanced Options negative). Also accepted at the top level as negativePrompt.
options.durationSecondsnoVideo duration.
options.resolutionnoResolution tier label.
Response (201)
{ "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).

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

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

EndpointPurpose
GET /charactersList characters.
GET /characters/:idGet one character.
GET /character-presetsInstalled character presets (base / edit / i2v).
GET /character-featuresAvailable identity/appearance feature options.
POST /charactersCreate a character (mode: "generate" from a description, or "upload" from an uploaded base image handle).
POST /characters/:id/generate-imageGenerate an image of the character ({ prompt, aspect?, count?, presetId? }).
POST /characters/:id/generate-frameGenerate a single video start frame.
POST /characters/:id/animateAnimate the character into a video.
POST /characters/:id/presetSet the character's default preset.
POST /characters/:id/enhanceUpscale/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.

EndpointPurpose
GET /recipe-blocksEntry points + every preset usable as a step, with its port contract.
POST /recipes/validateDry-run a spec (compose+validate) or a config, no save. Returns validation.
POST /recipesCompose from a spec and save a draft. Returns { recipeId, valid, errors, config }.
GET /recipesList saved recipe drafts.
GET /recipes/:idGet one saved recipe and its full config.
POST /recipes/:id/publishSubmit 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.

POST /recipes request
{
  "spec": {
    "name": "Product to spinning video",
    "category": "product",
    "steps": [
      { "preset": "krea2-pack:krea2-turbo" },
      { "preset": "wan-2.2-video-pack:wan-2.2-i2v" }
    ]
  }
}
Response (201)
{
  "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

HTTPerrorCodeWhen
400invalid_jsonBody is not valid JSON.
400invalid_optionsA required field is missing or malformed.
400invalid_presetPreset/pack/mediaType mismatch.
400needs_*A required input (prompt, image, references) is missing.
400invalid_handleAn input was not a valid upload handle.
400unsupported_backendPreset runs on an external API.
400preset_not_installedLocal backend chosen but models aren't installed.
400cloud_not_connectedCloud backend chosen but no account is connected.
401unauthorizedMissing or invalid token.
402exceeds_spend_capCloud cost exceeds the token's spend cap.
403forbidden_hostNon-loopback Host header.
404not_found / invalid_preset / job_not_foundUnknown route, preset, or job.
413asset_too_largeUpload over 100 MB.
503cost_unknownCapped token; cloud cost couldn't be determined.
500internal_errorUnexpected server error.