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.
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/assets
The house's captured assets, newest first
The body is { assets: [...] }, not a bare array — it was documented as a bare array until task #40 and never was one.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | folderId | string | no | Narrow to one collection |
Responses
| Status | Meaning |
|---|---|
200 | Asset list |
401 | No valid session |
200 returns:
{
"assets": [
{
"id": "string",
"name": "string",
"sku": "string",
"kind": "clothes",
"url": "string",
"folderId": "string",
"groupId": "string",
"mime": "string",
"tool": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/assets" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/assets/{id}
Stream one asset's bytes from R2 (owner-gated)
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | The image bytes (content-type from the asset row) |
401 | No valid session |
403 | Not the owner |
404 | Unknown or deleted |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/assets/{id}" \
-H "Authorization: Bearer $FLAM_API_KEY"PATCH /api/toolkit/assets/{id}
Rename, classify, tag or move one piece
An explicit folderId: null moves the piece back to the root. kind is stored as the reserved kind: tag and sku as the sku: tag, so both can be cleared with null. An empty patch is a 200 no-op, not an error.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | no | — |
sku | string | null | no | — |
kind | "clothes" | "accessory" | "jewelry" | "model" | "background" | no | — |
folderId | string | null | no | — |
{
"name": "string",
"sku": "string",
"kind": "clothes",
"folderId": "string"
}Responses
| Status | Meaning |
|---|---|
200 | The updated asset |
400 | BAD_BODY / BAD_KIND / BAD_SKU / BAD_FOLDER |
401 | No valid session |
404 | Unknown or foreign asset |
200 returns:
{
"asset": {
"id": "string",
"name": "string",
"sku": "string",
"kind": "clothes",
"url": "string",
"folderId": "string",
"groupId": "string",
"mime": "string",
"tool": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
}
}Call it
curl -X PATCH "https://api.flam.fashion/api/toolkit/assets/{id}" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","sku":"string","kind":"clothes","folderId":"string"}'GET /api/toolkit/assets/{id}/file
Stream one asset's bytes (the contract serving route)
Where every url in a list response points. Owner-gated, and a FOREIGN id answers 404, never 403 — this route never confirms that someone else's asset exists. (The legacy /api/toolkit/assets/{id} answers 403 for a foreign id; it is kept only for old links.)
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | The image bytes (Cache-Control private, max-age=3600; ETag from R2) |
401 | No valid session |
404 | Unknown |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/assets/{id}/file" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/assets/group
Stamp a fresh shared group onto a set of pieces
Writes group:<uuid> onto every named asset, REPLACING any prior group (re-grouping moves a piece). All-or-nothing: every id must be one of the caller's live assets or nothing is written.
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
assetIds | string[] | yes | Unique, non-empty. Duplicates are rejected. |
name | string | no | Accepted and currently unused |
{
"assetIds": [
"string"
],
"name": "string"
}Responses
| Status | Meaning |
|---|---|
201 | The new group id |
400 | BAD_BODY / BAD_ASSET_IDS |
401 | No valid session |
404 | One of the ids is unknown |
201 returns:
{
"groupId": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/assets/group" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"assetIds":["string"],"name":"string"}'POST /api/toolkit/assets/upload
Hang one image in the house library (multipart)
Bytes → R2, row → assets. The upload itself is FREE.
SMART UPLOAD — classify=1 is an OPT-IN read priced at 1 token per image (the describe-item classifier, run in-process after the row is written). It files the piece into its wardrobe bucket (kind) and gives it a human name — "IMG_2489.jpeg" becomes "white ribbed lace trim henley", a model collage comes back kind=model. Anything you state yourself wins: a supplied name/kind is never overwritten, and sku is your own code — it is never invented. Omit classify and nothing is read and nothing is charged.
A failed read never loses the upload and never charges: 201 with the asset unclassified and classify.ok=false carrying the reason (INSUFFICIENT_TOKENS, AI_NOT_CONFIGURED, AI_FAILED, …).
Request body — multipart/form-data (required)
| Field | Type | Required | Notes |
|---|---|---|---|
file | file | yes | — |
folderId | string | no | Target collection; omit for the root |
name | string | no | Your own label; wins over the smart read |
kind | "clothes" | "accessory" | "jewelry" | "model" | "background" | no | Your own wardrobe bucket; wins over the smart read |
sku | string | no | The brand's own code |
classify | "1" | "true" | "on" | "0" | no | Opt in to the 1-token smart read |
Responses
| Status | Meaning |
|---|---|
201 | The hung asset (+ classify when the read was requested) |
400 | NO_FILE / UNSUPPORTED_TYPE / EMPTY_FILE / BAD_KIND / BAD_SKU / BAD_FORM |
401 | No valid session |
404 | Unknown or foreign folderId |
413 | IMAGE_TOO_LARGE (12 MB cap) |
201 returns:
{
"asset": {
"id": "string",
"name": "string",
"sku": "string",
"kind": "clothes",
"url": "string",
"folderId": "string",
"groupId": "string",
"mime": "string",
"tool": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
},
"classify": {
"ok": true,
"nameEn": "string",
"kind": "string",
"error": "string",
"tokens": 0
}
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/assets/upload" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-F "[email protected]" \
-F "folderId=<folderId>" \
-F "name=<name>" \
-F "kind=<kind>" \
-F "sku=<sku>" \
-F "classify=<classify>"GET /api/toolkit/backgrounds
The whole library a look can stand on
Two sources, one call — the house's curated grounds (static, shipped with the code) and this director's own scenes (assets tagged kind:background, uploaded through /api/toolkit/assets/upload). Only the PUBLIC projection of a ground crosses the wire; the prompt clause it contributes stays server-side.
Responses
| Status | Meaning |
|---|---|
200 | Grounds + the house's own sets |
401 | No valid session |
200 returns:
{
"grounds": [
{
"id": "string",
"kind": "string",
"name": "string",
"note": "string",
"tone": "string",
"previewUrl": "string"
}
],
"scenes": [
{
"id": "string",
"name": "string",
"url": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/backgrounds" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/folders
Every collection in the house, flat
Oldest first. The client builds the tree from parentId.
Responses
| Status | Meaning |
|---|---|
200 | The folder list |
401 | No valid session |
200 returns:
{
"folders": [
{
"id": "string",
"name": "string",
"parentId": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/folders" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/folders
Create a collection
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | — |
parentId | string | null | no | Nest under an owned folder |
{
"name": "string",
"parentId": "string"
}Responses
| Status | Meaning |
|---|---|
201 | The created folder |
400 | BAD_BODY / NAME_REQUIRED |
401 | No valid session |
404 | Unknown or foreign parentId |
201 returns:
{
"folder": {
"id": "string",
"name": "string",
"parentId": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
}
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/folders" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","parentId":"string"}'PATCH /api/toolkit/folders/{id}
Rename a collection
A no-op rename (same name, or none supplied) still answers 200 with the folder.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | no | — |
{
"name": "string"
}Responses
| Status | Meaning |
|---|---|
200 | The folder |
400 | BAD_BODY |
401 | No valid session |
404 | Unknown or foreign id |
200 returns:
{
"folder": {
"id": "string",
"name": "string",
"parentId": "string",
"createdAt": "2026-07-27T09:00:00.000Z"
}
}Call it
curl -X PATCH "https://api.flam.fashion/api/toolkit/folders/{id}" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string"}'