Jobs
7 Jobs routes on the FLAM API: The house's develop RUNS, newest first; Stop a whole run — honestly; Reconnect backfill — jobs newer than an eventSeq.
Base URL https://api.flam.fashion. Send Authorization: Bearer flam_sk_… on every call; a handful of routes are session-only and say so. How keys and roles work.
GET /api/toolkit/develops
The house's develop RUNS, newest first
A run is DERIVED, not stored — params.runId, else the lookbook batch (lookId + stage), else same tool within a time gap. state=active keeps only runs with a frame still queued or rendering; the counts stay WHOLE (a 2-of-5 run still reports 5).
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | state | "active" | "all" | no | — |
| query | limit | integer | no | — |
Responses
| Status | Meaning |
|---|---|
200 | The runs |
400 | BAD_STATE / BAD_LIMIT |
401 | No valid session |
200 returns:
{
"runs": [
{
"id": "string",
"kind": "lookbook",
"tool": "string",
"title": "string",
"stage": "lock",
"lookId": "string",
"state": "queued",
"counts": {
"total": 0,
"done": 0,
"failed": 0,
"cancelled": 0,
"active": 0
},
"createdAt": "2026-07-27T09:00:00.000Z",
"finishedAt": "2026-07-27T09:00:00.000Z",
"thumbAssetId": "string",
"jobIds": [
"string"
],
"againCost": 0
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/develops" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/develops/{id}/cancel
Stop a whole run — honestly
A frame still QUEUED is cancelled and its hold RELEASED. A frame already RENDERING finishes and still costs: the model call is paid for and cannot be aborted. The response says both numbers out loud rather than implying a refund that is not happening.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | A develop-run id (run.<uuid> / look.<uuid>.<stage>) |
Responses
| Status | Meaning |
|---|---|
200 | What actually stopped |
401 | No valid session |
404 | Unknown |
200 returns:
{
"id": "string",
"cancelled": 0,
"cancelledJobIds": [
"string"
],
"stillRendering": 0,
"tokensReleased": 0,
"run": {
"id": "string",
"kind": "lookbook",
"tool": "string",
"title": "string",
"stage": "lock",
"lookId": "string",
"state": "queued",
"counts": {
"total": 0,
"done": 0,
"failed": 0,
"cancelled": 0,
"active": 0
},
"createdAt": "2026-07-27T09:00:00.000Z",
"finishedAt": "2026-07-27T09:00:00.000Z",
"thumbAssetId": "string",
"jobIds": [
"string"
],
"againCost": 0
}
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/develops/{id}/cancel" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/jobs
Reconnect backfill — jobs newer than an eventSeq cursor
The session user's generation_jobs with event_seq > since, ascending, in the same Job shape the WebSocket pushes. The web client calls this on every WS (re)open with its max seen eventSeq so events missed while the socket was down replay. since=0 returns everything.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | since | integer | no | — |
Responses
| Status | Meaning |
|---|---|
200 | Missed job events, ascending by eventSeq |
400 | BAD_SINCE |
401 | No valid session |
200 returns:
{
"jobs": [
{
"id": "string",
"tool": "string",
"status": "queued",
"resultUrl": "string",
"resultAssetId": "string",
"error": "string",
"brandId": "string",
"createdAt": "2026-07-27T09:00:00.000Z",
"eventSeq": 0
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/jobs" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/jobs/{id}/cancel
Stop ONE frame
Same honesty as the run-level stop. cancelled: false carries a reason — ALREADY_RENDERING (paid for, still coming) or ALREADY_SETTLED.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | The frame's true state |
401 | No valid session |
404 | NOT_FOUND — unknown or foreign (never a 403 that leaks existence) |
200 returns:
{
"id": "string",
"cancelled": true,
"state": "queued",
"tokensReleased": 0,
"reason": "ALREADY_RENDERING"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/jobs/{id}/cancel" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/jobs/{id}/duplicate
Run the same frame again as a NEW job
A fresh reserve, and that is correct — it is a new model call. Params are copied VERBATIM including the destination, so the quote covers any pass that destination forces. The source job is never re-billed or mutated.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Request body — application/json
| Field | Type | Required | Notes |
|---|---|---|---|
idempotencyKey | string | no | Makes one gesture safe to retry. Without it a double-click reserves twice and enqueues two paid model calls. |
{
"idempotencyKey": "string"
}Responses
| Status | Meaning |
|---|---|
202 | Queued |
401 | No valid session |
402 | INSUFFICIENT_TOKENS { need, spendable } — nothing created, nothing charged |
403 | A viewer may not spend the house's tokens |
404 | NOT_FOUND |
422 | UNKNOWN_TOOL / NO_INPUTS — never charged for a job that cannot run |
202 returns:
{
"jobId": "string",
"status": "queued",
"cost": 0
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/jobs/{id}/duplicate" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"idempotencyKey":"string"}'POST /api/toolkit/jobs/submit
Submit an async generation job
Generic over tools and N named images: tool must be a known async slug; every file field stages as a named input (field name = role, e.g. try-on sends model + garment); other string fields become the tool's params. Reserves a token hold before enqueue; idempotent on (user, idempotencyKey). Progress is pushed over the jobs WebSocket.
Request body — multipart/form-data (required)
| Field | Type | Required | Notes |
|---|---|---|---|
tool | string | yes | One of ASYNC_TOOL_SLUGS (upscale, reframe, generate-character, packshot, camera-lens, harmonize, tryon, iron, cleanup, color-change, fit-adjust, lookbook-video) |
idempotencyKey | string | no | — |
brandId | string | no | — |
image | file | no | Single-image tools; multi-input tools use per-role field names instead |
Responses
| Status | Meaning |
|---|---|
202 | Job queued (or the existing job on an idempotent replay) |
400 | UNKNOWN_TOOL | NO_IMAGE | EMPTY_IMAGE | BAD_FORM |
401 | No valid session |
402 | INSUFFICIENT_TOKENS — body includes need + spendable |
413 | IMAGE_TOO_LARGE (12 MB per image) |
500 | SUBMIT_FAILED |
202 returns:
{
"jobId": "string",
"status": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/jobs/submit" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-F "tool=<tool>" \
-F "idempotencyKey=<idempotencyKey>" \
-F "brandId=<brandId>" \
-F "[email protected]"GET /api/toolkit/jobs/ws
Job event stream (WebSocket upgrade)
Upgrades to a WebSocket forwarded to the caller's per-user JobStreamDO. Every job status change is pushed as {"type":"job","job":Job}. Requires the session cookie on the handshake.
Responses
| Status | Meaning |
|---|---|
101 | Switching Protocols — socket attached |
401 | No valid session |
426 | Upgrade header missing — not a WebSocket request |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/jobs/ws" \
-H "Authorization: Bearer $FLAM_API_KEY"Assets
10 Assets routes on the FLAM API: The house's captured assets, newest first; Stream one asset's bytes from R2 (owner-gated); Rename, classify, tag or.
Lookbook
9 Lookbook routes on the FLAM API: The house's looks, newest first (50); Create a look from the director's three choices; One look, whole — selections.