Share
9 Share routes on the FLAM API: What this house has sent; Mint a link a brand can open with no account; Close a link now.
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/share
What this house has sent
Newest first. answered and kept are COUNTED AT READ TIME from share_mark — there is no stored rollup anywhere in this feature, because a stored rollup drifts the first time a mark changes and then the board lies, which is worse than no board.
A revoked or expired link still appears here WITH ITS MARKS. Expiry closes a door; it does not retract an opinion, and the marks are hers forever.
Responses
| Status | Meaning |
|---|---|
200 | Her sends |
401 | Unauthenticated |
200 returns:
{
"shares": [
{
"id": "string",
"token": "string",
"recipientLabel": "string",
"frames": 0,
"answered": 0,
"kept": 0,
"expiresAt": "string",
"revokedAt": "string",
"createdAt": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/share" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/share
Mint a link a brand can open with no account
{ assetIds, recipientLabel? } — freezes the selection into an ORDERED SNAPSHOT and returns { url }. The link is {WEB_ORIGIN}/s#<token>: the token is in the FRAGMENT, which is never sent to any server, so it appears in no access log, no API log, no Referer and no proxy.
A share is a snapshot, never a live view. Adding a frame to the lookbook afterwards does not change an open link — an approval is an approval of what the approver SAW. She wants the brand to see two more frames? She sends a second link.
recipientLabel is free text SHE types ("Maya at Ganni"), or nothing, in which case every surface says "the link holder". FLAM never emails the recipient and never cookies them.
COSTS NOTHING. No job, no tool slug, no reserve, no price-book row.
Request body — application/json
| Field | Type | Required | Notes |
|---|---|---|---|
assetIds | string[] | yes | — |
recipientLabel | string | no | — |
email | string (email) | no | — |
message | string | no | — |
{
"assetIds": [
"string"
],
"recipientLabel": "string",
"email": "string",
"message": "string"
}Responses
| Status | Meaning |
|---|---|
200 | The link, ready to copy |
400 | No frames, more than 60 of them, or a bad recipient address |
401 | Unauthenticated |
404 | None of those frames are this house's |
429 | Too many links minted by this house in an hour |
200 returns:
{
"id": "string",
"url": "string",
"token": "string",
"frames": 0,
"emailed": "sent"
}Call it
curl -X POST "https://api.flam.fashion/api/share" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"assetIds":["string"],"recipientLabel":"string","email":"string","message":"string"}'POST /api/share/{id}/revoke
Close a link now
Stamps revoked_at. Enforced on EVERY request to every public door from the next one onward — never by hiding a row from a list, and never by the client.
THE HONEST SENTENCE, and the docs page says it too: revoking stops new reads; it cannot un-download what was already downloaded. The share bytes carry max-age=3600, so an hour bounds how long they survive in a browser that already has them.
The marks stay. They are hers.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | Closed |
401 | Unauthenticated |
404 | Not this house's link |
200 returns:
{
"revoked": true
}Call it
curl -X POST "https://api.flam.fashion/api/share/{id}/revoke" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/share/{token}
Open a send (no account, no session)
The manifest a brand's decision-maker's browser reads. No requireActor — authority is the 256-bit token, which lives in the page's URL FRAGMENT and therefore reaches no server log, no Referer and no proxy.
It answers with LABELS AND POSITIONS ONLY: no asset ids, job ids, user ids, org id, R2 keys, judge verdicts, scores, SKUs, lineage or prices. Frames are addressed by i — an index into this share's own ordered array — so there is nothing to enumerate.
Expired, revoked and never-existed are BYTE-IDENTICAL 404s, on purpose.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | token | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | The frames she sent, in her order |
404 | Expired, revoked, or never existed — one body for all three |
200 returns:
{
"house": "string",
"recipientLabel": "string",
"expiresAt": "string",
"finishedAt": "string",
"frames": [
{
"i": 0,
"name": "string",
"note": "string",
"w": 0,
"h": 0,
"mark": "keep",
"markNote": "string",
"drawings": null
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/share/{token}" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/share/{token}/f/{i}
The bytes of one frame in a send
Full quality, byte-for-byte the original R2 object — no re-encode, no watermark, no downscale. ?thumb=1 serves the small one when the house holds one and falls back to the original when it does not. ?export=1 means the recipient is TAKING the bytes, and writes the same first-party keep signal the library writes (never on a thumbnail — a wall is not a keep).
Cache-Control: private, max-age=3600 and NOT the library's year-long immutable: revocation cannot reach into a cache, so an hour bounds how long a revoked link's bytes survive in the recipient's browser. Revoking stops new reads; it cannot un-download what was already taken.
i is a position in this share's array. Past its end is a 404, like everything else here.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | token | string | yes | — |
| path | i | string | yes | — |
| query | thumb | string | no | — |
| query | export | string | no | — |
Responses
| Status | Meaning |
|---|---|
200 | The image. |
404 | Expired, revoked, out of range, or never existed |
Call it
curl -X GET "https://api.flam.fashion/api/share/{token}/f/{i}" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/share/{token}/finish
Say you are done
The end of the walk. A reviewer never really signs off — they just stop — so until this existed there was no moment the house could tell the sender about. This is them SAYING so, which is a different fact from the marks.
IDEMPOTENT. Finishing twice keeps the FIRST stamp and writes no second notification, because "when did they tell me they were done" has one honest answer. It does NOT close the link: they may reopen it, change a mark and finish again.
Answering with nothing marked is refused — there is no news in it.
No account, no session, no tokens, rate-limited like the mark door.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | token | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | Their answers are with her |
400 | Nothing was marked, so there is nothing to send |
404 | Expired, revoked, or never existed |
200 returns:
{
"finished": true,
"kept": 0,
"passed": 0
}Call it
curl -X POST "https://api.flam.fashion/api/share/{token}/finish" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/share/{token}/mark
Say keep or pass on one frame
{ i, verdict, note?, drawings? } — an UPSERT keyed on (share, frame), so a reviewer may change their mind and the last word stands. One mark per frame per LINK, which is one mark per recipient per frame: two decision-makers are two links.
THE HANDLER TAKES NO ASSET ID FROM THE WIRE. It resolves share.asset_ids[i] server-side, so a mark can only ever touch a frame inside this share — narrow by construction, not by a check.
drawings is COORDINATES: [{kind:'circle'|'free', points:[[x,y],…]}], every coordinate a finite float in [0,1], at most 3 strokes of at most 120 points. SVG, HTML, a d string, an object, NaN or a coordinate of 1.5 are each a 400 — the recipient sends numbers, and the house draws them in its own ink.
This writes ONE notification per share, on the FIRST mark. The count she reads ("12 of 14") is joined at READ time, so one row stays true as the reviewer keeps going.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | token | string | yes | — |
Request body — application/json
| Field | Type | Required | Notes |
|---|---|---|---|
i | integer | yes | — |
verdict | "keep" | "pass" | yes | — |
note | string | no | — |
drawings | ShareDrawings | no | — |
{
"i": 0,
"verdict": "keep",
"note": "string",
"drawings": [
{
"kind": "circle",
"points": [
null
],
"note": "string"
}
]
}Responses
| Status | Meaning |
|---|---|
200 | The mark stands |
400 | A note over 600 characters, or a drawing that is not numbers |
404 | Expired, revoked, out of range, or never existed |
429 | Too many marks from one address in an hour |
200 returns:
{
"recorded": true
}Call it
curl -X POST "https://api.flam.fashion/api/share/{token}/mark" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"i":0,"verdict":"keep","note":"string","drawings":[{"kind":"circle","points":[null],"note":"string"}]}'GET /api/toolkit/folders/{id}/coverage
What is done in this collection, and what is still missing
DERIVED AT READ TIME. There is no status column, no stored rollup and no cached count anywhere in this feature — a stored rollup drifts the first time a mark, an approval or a look's cast changes, and then the board lies, which is worse than no board. The source rows ARE the state.
missing counts pieces with no director-approved frame. approved there means assets.approved_at IS NOT NULL — HER decision, never a recipient's keep. A recipient's enthusiasm is not completion.
Per piece, four states: covered (≥1 approved frame, or the piece's own packshot approved), changes-asked (an open pass came back and nothing of this piece is approved — an unresolved disagreement is unfinished work), awaiting (in a live share, nothing back yet), not-sent.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | The collection's coverage |
401 | Unauthenticated |
404 | Not this house's collection |
200 returns:
{
"pieces": 0,
"covered": 0,
"missing": 0,
"states": {}
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/folders/{id}/coverage" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/share-marks
What came back, on these frames
?assetIds=a,b,c — the recipients' marks on the named frames, for her library tiles and her lightbox. Org-scoped; a frame she does not own returns nothing rather than an error.
WHEN TWO RECIPIENTS DISAGREE, BOTH ROWS COME BACK AND NO RULE PICKS A WINNER. Maya keeping frame 7 while Tom passes it is not a data problem — it is the most important thing on the screen, and a house that silently computes a winner has hidden the one fact she needed. The tie is broken by her, in the decision layer.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | assetIds | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | Evidence, newest first |
401 | Unauthenticated |
200 returns:
{
"marks": [
{
"assetId": "string",
"recipientLabel": "string",
"verdict": "keep",
"note": "string",
"drawings": [
null
],
"at": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/share-marks?assetIds=<assetIds>" \
-H "Authorization: Bearer $FLAM_API_KEY"Postures
2 Postures routes on the FLAM API: The posture library, the control axes, and this house's preferences; Star, default, add to Explore, and save her own.
Support
6 Support routes on the FLAM API: Record a support chat that needed a human; Attach a screenshot to a support ticket; Open a screenshot from a support.