# StageSnap Developer API v1 Reference Auto-generated from convex/apiProxyPricing.ts, convex/apiProxyModelAllowlist.ts, and web/src/lib/generation/routes.ts — do not hand-edit. Regenerate with `npx tsx scripts/generate-api-proxy-llms-txt.ts` from the `web/` directory whenever pricing or the model allowlist changes. A drift-guard test (web/src/lib/api-proxy-llms-txt.test.ts) fails CI if this file is stale. This file is a complete, self-contained API reference intended to be pasted directly into an AI coding assistant so it can integrate against the StageSnap Developer API without further lookups. ## Authentication All requests use a bearer API key: ``` Authorization: Bearer sk_live_ ``` Get a key from the StageSnap dashboard (Settings → API Keys). Test keys use the `sk_test_` prefix and behave identically except where noted. ## Currency: API credits 1 API credit = $0.01 USD. API credits are a SEPARATE balance from any in-app subscription/studio credits — top up via `POST /api/stripe/api-credits/checkout`. Packs: 1,000 credits ($10), 5,000 credits ($45), 20,000 credits ($160). ## Endpoints ### POST /api/v1/text Generate text with StageSnap's server-selected model. Fixed price: **4 API credits**. Provider credentials and model routing remain inside StageSnap. Send 1-20 messages with roles `system`, `user`, or `assistant`; combined content is capped at 50,000 characters. `max_tokens` defaults to 4,096 and may be 256-8,192. Optional `response_format: { "type": "json_object" }` requests JSON output. An `Idempotency-Key` header (8-128 characters) is required. Retrying the same request with the same key replays the stored result without charging or calling the provider again. Reusing a key for different input returns `409`. **Request:** ```json { "messages": [ { "role": "system", "content": "Return a JSON article draft." }, { "role": "user", "content": "Write an Austin first-time buyer guide." } ], "max_tokens": 4096, "temperature": 0.4, "response_format": { "type": "json_object" } } ``` **Response (200):** ```json { "success": true, "content": "{\"title\":\"Austin First-Time Buyer Guide\"}", "usage": { "promptTokens": 120, "completionTokens": 900, "totalTokens": 1020 }, "creditsCharged": 4, "idempotencyKey": "growth-run-123", "replayed": false } ``` Errors: `400` invalid input or missing idempotency key; `402` insufficient API credits; `409` an idempotency conflict/request still processing; `413` body over 64KB; `502` provider failure (the charge is refunded); `503` server configuration error. The endpoint is hidden behind `API_PROXY_ENABLED=true`; while disabled it returns `404`. ### POST /api/v1/proxy/generate Submit a generation job. Provide exactly one of `route` (a registered, fixed-price route id — see the price table below) or `model` (a raw KIE model id from the dynamic catalog table below). `input` is forwarded to KIE **verbatim** for model parameters (prompt, resolution, image URLs, duration, etc.) — every generation parameter the underlying KIE model accepts works through this proxy, we do not allowlist those. Transport/ control fields (`callBackUrl`/`callbackUrl`, `webhookUrl`, `authorization`, `apiKey`, `headers`) are always stripped before forwarding — the proxy manages its own callback; you cannot redirect KIE's webhook via `input`. Request bodies are capped at 1MB. **Pricing for fixed routes is read directly from `input`** (resolution, duration, audio flag — see the price table below for which fields each route's price depends on). There is no separate pricing field: the price you're charged is always computed from the exact values that get forwarded to KIE, so there's no way to under-declare what you're requesting. If a registered route's `input` doesn't match any known priced tier (e.g. an unlisted resolution), the call is NOT rejected — it automatically falls back to dynamic reserve-then-settle pricing for that one call (see below), so every parameter combo KIE itself accepts is always usable, never under-charged. **Request (fixed route):** ```json { "route": "kie:nano-banana-2", "input": { "prompt": "stage this living room in a modern style", "image_input": ["https://your-cdn.example/room.jpg"], "resolution": "2K" }, "idempotencyKey": "optional-client-dedupe-token" } ``` **Request (dynamic model):** ```json { "model": "bytedance/seedance-2-fast", "input": { "first_frame_url": "https://your-cdn.example/start.jpg", "last_frame_url": "https://your-cdn.example/end.jpg", "duration": 6 } } ``` **Response (200):** ```json { "success": true, "taskId": "kie-task-id", "creditsCharged": 9, "status": "queued" } ``` Dynamic-model responses add `"pricingMode": "dynamic"` — `creditsCharged` for those is a **reservation**, refined down to actual cost once the task completes (see Reserve-then-settle below); poll the task to see the final number reflected in your balance. **Error responses:** `400` invalid body / unknown route / unpriced dynamic model not in the allowlist (`model_not_supported`); `402` insufficient API credits (balance in the `X-API-Credit-Balance` header); `413` body over 1MB; `404` proxy disabled or unknown model id; `502` KIE submission failed (any charge is automatically refunded). `idempotencyKey` (optional): client dedupe token; identical key+body replays the prior task without re-charging or re-submitting. After a failed submission that was auto-refunded, retry with a **fresh** `idempotencyKey` — reusing the same key after a failed/refunded call returns `500`, not a successful retry. ### POST /api/v1/stage StageSnap's own virtual-staging engine (our prompt stack, style catalog, and room-type handling) as opposed to the raw model passthrough in `/api/v1/proxy/generate`. Fixed price: **24 API credits** per call — the caller supplies zero pricing input (no resolution, duration, or tier knobs). Request bodies are capped at 1MB. **Room Intel (v1 limitation):** this endpoint takes a raw `image_url`, not an uploaded asset with a Room Intel classification record. Live room classification is not run. Optional `room_type` defaults to `"auto"`; when `"auto"` or omitted the staging prompt runs without an explicit room type (no `roomIntel` object is attached). **Request:** ```json { "image_url": "https://your-cdn.example/room.jpg", "style": "modern", "room_type": "auto", "idempotencyKey": "optional-client-dedupe-token" } ``` - `image_url` (required): publicly reachable URL of the room photo to stage. - `style` (optional): one of the style catalog ids listed below (e.g. `modern`, `minimalist`, `farmhouse`, `scandinavian`). - `room_type` (optional, default `"auto"`): free-form room type string; unknown values are accepted and normalized by the prompt stack. - `idempotencyKey` (optional): client dedupe token; identical key+body replays the prior task without re-charging or re-submitting. After a failed submission that was auto-refunded, retry with a **fresh** `idempotencyKey` — reusing the same key after a failed/refunded call returns `500`, not a successful retry. **Style catalog (live from the product style maps):** `modern`, `minimalist`, `traditional`, `farmhouse`, `coastal`, `industrial`, `scandinavian`, `midcentury`, `bohemian`, `luxury`, `transitional`, `contemporary`, `artdeco`, `japandi`, `mediterranean`, `rustic`, `french`, `hollywood`, `tropical`, `urban`, `biophilic`, `cottage`, `asian`, `southwestern`, `eclectic`, `curb_appeal`, `modern_exterior`, `traditional_exterior`, `mediterranean_exterior`, `farmhouse_exterior`, `tropical_exterior`, `desert_exterior`, `cottage_exterior` **Response (200):** same shape as `/api/v1/proxy/generate` fixed-route success: ```json { "success": true, "taskId": "kie-task-id", "creditsCharged": 24, "status": "queued" } ``` Poll status via the same `GET /api/v1/proxy/tasks/{taskId}` endpoint documented below (tasks are stored with `route: "kie:nano-banana-2"` and `pricingMode: "fixed"`, so the existing poll path works unchanged). **Error responses:** `400` missing `image_url` / invalid style / invalid JSON; `402` insufficient API credits (`X-API-Credit-Balance` header); `413` body over 1MB; `503` when the proxy surface is disabled (same `media_pipeline_unavailable` body as legacy generation containment); `502` KIE submission failed (any charge is automatically refunded). ### GET /api/v1/proxy/tasks/{taskId} Poll task status. Scoped to the API key that created the task — a task created by a different key returns `404` (indistinguishable from a nonexistent task, by design). **Response (200):** ```json { "success": true, "taskId": "kie-task-id", "status": "completed", "resultUrls": ["https://kie-cdn.example/output.png"], "creditsCharged": 9 } ``` `status` is one of `queued` | `processing` | `completed` | `failed`. ### GET /api/v1/proxy/balance **Response (200):** ```json { "success": true, "balanceCredits": 4820 } ``` ## Reserve-then-settle (dynamic models) Models not in the fixed price table are charged a per-class **reservation** at submit time, then settled to actual cost once the task completes: - **image**: 25 credits - **video**: 400 credits - **audio**: 100 credits - **other**: 100 credits Settlement formula: `actualCredits = ceil(kieCreditsConsumed * $0.005 * 1.5 / $0.01)` (KIE's own reported credit usage, converted to USD, marked up 1.5x, rounded up to the next whole API credit). The difference between the reservation and the actual cost is refunded to your balance automatically the first time you poll the task after it completes. Failed or unbilled tasks are fully refunded. ## Fixed-price routes | Route | Tier | Credits | |---|---|---| | `kie:nano-banana-edit` (kie Nano Banana Edit) | per image | 3 | | `kie:nano-banana-pro` (kie Nano Banana Pro) | 1K/2K | 14 | | | 4K | 18 | | `kie:nano-banana-2` (kie Nano Banana 2) | 1K | 6 | | | 2K | 9 | | | 4K | 14 | | `kie:flux-kontext-pro` (kie FLUX Kontext Pro) | per image | 4 | | `kie:seedream-4.5-edit` (kie Seedream 4.5 Edit) | per image | 5 | | `kie:gpt-image-2` (kie GPT Image 2) | 1K | 5 | | | 2K | 8 | | | 4K | 12 | | `kie:qwen-image-edit` (kie Qwen Image Edit) | image_size: "square" (~1MP) | 2 | | `kie:veo-3.1` (kie Veo 3.1) | flat, per video (no caller-selectable tier) | 300 | | `kie:hailuo-2.3-i2v` (kie Hailuo 2.3 Image to Video) | 6s, 768P | 33 | | | 10s, 768P | 68 | | | 6s, 1080P | 59 | | `kie:kling-2.5-turbo-pro-pin-to-pin` (kie Kling 2.5 Turbo Pro Pin-to-Pin) | 5s | 32 | | | 10s | 63 | | `kie:kling-2.6-i2v` (kie Kling 2.6 Image to Video) | 5s, no audio | 42 | | | 10s, no audio | 83 | | | 5s, with audio | 83 | | | 10s, with audio | 165 | | `kie:seedance-2-i2v` (kie Seedance 2 Image to Video) | 480P, per second | 12 | | | 720P, per second | 25 | | | 1080P, per second | 77 | | | 4K, per second | 156 | | `kie:wan-2.7-i2v` (kie Wan 2.7 Image to Video) | 720p, per second | 12 | | | 1080p, per second | 18 | | `kie:grok-imagine-i2v` (kie Grok Imagine Image to Video) | 6s/480p | 8 | | | 6s/720p | 15 | | | 10s/480p | 15 | | | 10s/720p | 23 | ## Dynamic-pricing models (reserve-then-settle) | KIE Model ID | Class | Reserve Cap (credits) | Notes | |---|---|---|---| | `seedream/5-pro-image-to-image` (Seedream 5 Pro Image to Image) | image | 25 | Distinct from the fixed seedream/4-5-edit route. Max 2K (not 4K) per source docs. | | `topaz/image-upscale` (Topaz Image Upscale) | image | 50 | Factor-based (1/2/4/8x), no resolution param — cost driven by output size, not input. No verified $/call in the source research; reserve bumped above the 25cr image default as a defensive margin for the 8x factor until real cost data lands (money review 2026-07-11). | | `bytedance/seedance-2-fast` (Seedance 2 Fast Image to Video) | video | 2500 | Worst case computed from the fixed kie:seedance-2-i2v route's verified 4K rate (156 credits/second, apiProxyPricing.ts) at the catalog's documented 15s max duration: 156 x 15 = 2340cr; reserved at 2500cr with margin (money review 2026-07-11 — the prior 400cr class default was well under this worst case, a revenue-loss risk since settle never re-charges above the reserve). | | `bytedance/seedance-1.5-pro` (Seedance 1.5 Pro Image to Video) | video | 1000 | Supports fixed_lens camera lock; up to 1080p (no 4K), 4-12s. Worst case computed from kie:seedance-2-i2v's verified 1080p rate (77cr/s) at 12s = 924cr; reserved at 1000cr (money review 2026-07-11). | | `kling/v2-1-pro` (Kling V2.1 Pro Image to Video) | video | 400 | — | | `kling-3.0/video` (Kling 3.0 Image to Video) | video | 600 | Up to 15s at 4K (3840x2160). Worst case per the source research's own $0.07/s figure: 0.07 x 15 x 1.5 x 100 = ~158cr; 600cr reserved is already well above that (money review 2026-07-11 — kept conservative in case the $0.07/s figure undercounts 4K specifically). | | `kling/v3-turbo-image-to-video` (Kling V3 Turbo Image to Video) | video | 400 | — | | `hailuo/02-image-to-video-pro` (Hailuo 02 Pro Image to Video) | video | 400 | — | | `hailuo/02-image-to-video-standard` (Hailuo 02 Standard Image to Video) | video | 400 | — | | `topaz/video-upscale` (Topaz Video Upscale) | video | 800 | Factor-based (1/2/4x), no verified $/s in the source research — cost likely scales with output resolution AND input footage duration, both uncapped by us. Bumped above the 400cr video default as a defensive margin pending real cost data (money review 2026-07-11). | Full parameter schemas for dynamic models are documented on KIE's own docs site at `https://docs.kie.ai/market//` — the model id above is the exact string to pass as `input`'s sibling `model` field; every parameter that model's KIE docs page lists is accepted verbatim. ## Not supported Suno (music), avatar/lipsync models (Kling AI Avatar, OmniHuman, Infinitalk), and Luma/Pika/Vidu/Flux ControlNet-depth are not available through this proxy — the first two use a different KIE API surface than the generic job model this proxy wraps, and the last group isn't in KIE's current API catalog at all. ## Rate limits - Per API key: 60 requests/minute (all endpoints). - Global proxy throttle: capped below KIE's own ≤20 requests/10s platform ceiling to leave headroom for other StageSnap traffic also calling KIE. A 429 response includes `Retry-After`. ## Errors All error responses share this shape: ```json { "success": false, "error": "human-readable message" } ```