# StableKey API VFX-grade green-screen unmixing via Corridor Digital's CorridorKey neural keyer. Stablekey runs CorridorKey on Modal L40S GPUs, takes a green-screen plate from StableUpload, and returns clean alpha + un-multiplied straight foreground + premultiplied RGBA mp4s ready for compositing. Base URL: `https://stablekey.dev` Operated at cost-plus-defensive-buffer (2× modeled cost). Surplus is reinvested into capacity, not extracted as profit. Model © Corridor Digital. Star their repo: https://github.com/nikopueringer/CorridorKey ## What you get back For every job, three mp4s land at agent-provided StableUpload public URLs: - **alpha** — the predicted alpha matte (linear). Grayscale mp4, sharp at hair / motion-blur / translucency edges where most "AI roto" tools give you a binary mask. - **fg** — the recovered straight (un-multiplied) foreground in sRGB. The green spill has been mathematically undone, not just suppressed. To composite, convert to linear before multiplying by alpha. - **processed** — the production output: linear premultiplied RGBA encoded as RGB-on-black mp4 for easy preview. Drop into Premiere/Resolve on top of a new background and it just works. ## Required Workflow StableKey does not accept raw file uploads. Per D8 in the architecture: the agent reserves every storage slot itself on stableupload.dev. There is no cross-app billing. ``` 1. POST stableupload.dev /api/upload # input plate → upload bytes to returned uploadUrl 2. POST stableupload.dev /api/upload × 3 # output reservations → DO NOT upload bytes; forward the URLs to stablekey 3. POST stablekey.dev /api/process # paid: GPU compute only → returns { jobId } 4. GET stablekey.dev /api/jobs/{jobId} # poll every 5-10s ``` For "gnarly" shots (see below), insert a step between 1 and 2: `POST stablesam.dev /api/segment` with prompt "person", upload the resulting mask mp4 to stableupload, and pass that URL as `hintUrl` on /api/process. ## Hint generator (default: inline chroma) Stablekey generates the coarse alpha hint inline using a fast chroma threshold + dilate + blur. **For clean studio plates this is empirically equivalent in quality to SAM-3.1-generated hints.** No setup, no extra calls, no extra cost — just submit `/api/process` without `hintUrl`. **Bring your own `hintUrl`** (typically by calling `stablesam.dev /api/segment` first) only for gnarly shots: - subject is wearing green clothing (chroma threshold will erase it) - complex non-green BG props (lighting equipment, etc.) you don't want keyed in - multiple subjects in frame, you only want some of them - translucent FG objects (smoke, glass, sheer fabric) where green shows through partly - visible tracking-dot crosses on the green wall (chroma will pick them up) The added cost is ~$0.05/clip for stablesam plus an extra StableUpload tier purchase for the mask mp4. ## Internal model resolution: always 2048×2048 CorridorKey resizes every input plate to a 2048×2048 working grid for inference, then resizes outputs back to your declared `resolutionWidth × resolutionHeight`. Two practical consequences: - **Cost is sub-linear in input resolution.** Empirical (L40S compile=default B=1, 250-frame 1080p smoke test): 4K is ~1.7× the per-frame cost of 1080p, not 4×. **Don't downscale your input to save money** — submit at the resolution your output needs. - **Aspect ratio is preserved by default.** Non-square inputs are letterboxed to 2048² and cropped back. - **There is no quality benefit to inputs above 2048².** Anything larger gets downscaled before inference. ## Color space contract CorridorKey expects the input plate as sRGB by default; declare `colorSpace: "linear"` if your source is linear (e.g. EXR sequences re-encoded as mp4 in linear gamma). Get this wrong and the output looks washed out / crushed. **No refund on validation mismatch (D7).** Modal validates declared `frameCount`, `resolutionWidth`, `resolutionHeight` against ffprobe BEFORE running inference. Compute locally: ```bash ffprobe -v error -count_packets \ -show_entries stream=nb_read_packets,width,height \ -of csv=p=0 your_plate.mp4 ``` ## Idempotency Pass an opaque `clientRequestId` (UUID, ≤128 chars) on every retry. Same `(walletAddress, clientRequestId)` returns the existing job WITHOUT re-billing. ## /api/process — $0.05+ per video job ```json { "type": "stablekey-video", "rgbUrl": "https://f.stableupload.dev/abc123/plate.mp4", "declared": { "frameCount": 250, "resolutionWidth": 1920, "resolutionHeight":1080, "colorSpace": "srgb" }, "outputs": { "alpha": {"uploadUrl": "...", "publicUrl": "..."}, "fg": {"uploadUrl": "...", "publicUrl": "..."}, "processed": {"uploadUrl": "...", "publicUrl": "..."} }, "clientRequestId": "ck_2026_04_26_abc" } ``` Optional: `hintUrl` (gnarly shots), `options` (CorridorKey knobs). Pricing: `max(0.05, frames × (0.4 + max(0, mp - 2) × 0.085) / 3600 × $1.95 × 2)`. 250-frame 1080p ≈ $0.10. 250-frame 4K ≈ $0.15. ## /api/jobs/{jobId} — SIWX-authed status `{status: "queued"|"processing"|"complete"|"failed", result?: {outputs, metrics}, error?}`. Poll every 5-10s. Typical wall-time: ~2 min for a 250-frame 1080p clip. If `error.code` is `ValidationMismatch`, declared frameCount/resolution didn't match ffprobe; correct + retry with fresh `clientRequestId`. ## /api/jobs — SIWX-authed list Pagination via `?cursor=…&limit=50`. ## DELETE /api/jobs/{jobId} — soft-delete a finished job