Admin
43 Admin routes on the FLAM API: Grant / revoke toolkit or tool access (admin); Token/cost usage summary from the ledger (admin); The platform analyst's.
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.
POST /api/toolkit/admin/access
Grant / revoke toolkit or tool access (admin)
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
action | "grant_toolkit" | "revoke_toolkit" | "grant_tool" | "revoke_tool" | yes | — |
userId | string | no | — |
email | string | no | — |
toolkitId | string | no | — |
toolId | string | no | — |
{
"action": "grant_toolkit",
"userId": "string",
"email": "string",
"toolkitId": "string",
"toolId": "string"
}Responses
| Status | Meaning |
|---|---|
200 | { ok, count } |
400 | Missing action / item id / target |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/access" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"grant_toolkit","userId":"string","email":"string","toolkitId":"string","toolId":"string"}'GET /api/toolkit/admin/ai-costs
Token/cost usage summary from the ledger (admin)
Responses
| Status | Meaning |
|---|---|
200 | Settled tokens + real USD per tool, per day, and totals |
403 | Session email not in the admin allowlist |
200 returns:
{
"byTool": [
{
"tool": "string",
"tokens": 0,
"cost_usd": 0,
"n": 0
}
],
"daily": [
{
"day": "string",
"tokens": 0,
"cost_usd": 0
}
],
"totalTokensSettled": 0,
"totalCostUsd": 0
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/ai-costs" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/analyst
The platform analyst's latest 12-hour digest (admin only)
The latest thing the platform analyst wrote, plus the headlines behind it. The analyst itself runs in services/ai-worker on a 12-hour cron (0 */12 * * *, src/analyst.ts); this route only reads, and there is deliberately no POST — a digest that can be triggered from a browser moves the baseline the next delta is measured against. It is a DELTA, not a dashboard: /api/toolkit/admin/judge/rollup already prints the totals, and the product here is the sentence the rollup cannot say. thin: true means the analyst REFUSED to narrate — below the floor no model is called at all, so model is null, actions is empty and the headline states the count and the floor. movements are computed in TypeScript from two counted snapshots before any model exists in the story, each carrying both denominators, and rubricVersion plus judgeModel are part of every cell key so a rubric bump breaks the comparison instead of being reported as a quality change. Every actions entry is a PROPOSAL — nothing in FLAM applies one. Every read writes an admin_access_log row; counts and one sentence, no imagery, no prompt, no email, no house name.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | history | integer | no | How many previous headlines to return. |
Responses
| Status | Meaning |
|---|---|
200 | The latest digest with its movements and proposed actions, the previous headlines, and the caveats that say what a thin digest means |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/analyst" \
-H "Authorization: Bearer $FLAM_API_KEY"PATCH /api/toolkit/admin/analyst/cadence
Set the analyst's window (admin only)
D12's actual complaint, in one route — "cannot change its window without a deploy." The window was a WINDOW_HOURS constant marked /** Not negotiable */; it is now a row in analyst_config, and the next digest carries the window_hours it was written under so an old digest still says what it was measuring.
Persists. Writes an admin_access_log row.
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
hours | integer | yes | The lookback window in hours. 168 is a week — the founder's own override of the 12-hour default. |
enabled | boolean | no | Off means the run verb refuses. The analyst has no other trigger, so this is the whole switch. |
{
"hours": 0,
"enabled": true
}Responses
| Status | Meaning |
|---|---|
200 | The cadence as stored |
400 | A window outside the accepted range |
403 | Session email not in the admin allowlist |
Call it
curl -X PATCH "https://api.flam.fashion/api/toolkit/admin/analyst/cadence" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hours":0,"enabled":true}'POST /api/toolkit/admin/analyst/run
Run the analyst now (admin only)
D12: "it is not a schedule." The 12-hour cron is gone from services/ai-worker/wrangler.toml — the analyst runs when a person presses this, and never otherwise. An analysis nobody asked for is a row nobody reads and a baseline the next delta is silently measured against.
A SECOND PRESS INSIDE THE WINDOW COSTS NOTHING. The claim SQL in src/analyst.ts already refuses to start a second run inside window_hours, so this returns the digest that already exists rather than spending the model call again. That is what makes the button safe to put in front of a person.
The floor still holds: below the readable-verdict minimum the digest is thin, actions is empty and no model is called at all. Writes an admin_access_log row.
Responses
| Status | Meaning |
|---|---|
200 | The digest — freshly written, or the existing one when a run is already inside the window |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/analyst/run" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/develops
Develop runs across the house, newest first
Responses
| Status | Meaning |
|---|---|
200 | Runs |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/develops" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/develops/action
Retry or cancel a develop
Cancel carries the Darkroom's honest semantics — queued frames release their hold, a frame already rendering finishes and still costs.
Responses
| Status | Meaning |
|---|---|
200 | The action's honest tally |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/develops/action" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/emails
Every letter the house sent (email_log)
The list omits html on purpose — a hundred rendered emails is megabytes of body for a table that shows none of it. Pass ?id= for ONE row WITH its byte-exact archived HTML, exactly as the recipient received it. skipped is the dev-safe lane, not a fault.
Responses
| Status | Meaning |
|---|---|
200 | Letters, or one letter with its archive |
403 | Session email not in the admin allowlist |
404 | No such letter |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/emails" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/entitlements
Real-money purchases across the house
Responses
| Status | Meaning |
|---|---|
200 | Entitlements |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/entitlements" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/entitlements/revoke
Revoke a granted entitlement (a refund's other half)
Responses
| Status | Meaning |
|---|---|
200 | Revoked |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/entitlements/revoke" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/failures
Failures grouped by what the provider actually said
Twelve rows of "the provider returned no frame" is one problem wearing twelve uuids, so failures group on the error text with ids and digits knocked out. Each group carries how many HOUSES it reached — one house is probably their input, several is ours — and the whole real message from the most recent occurrence. Refused emails ride along.
Responses
| Status | Meaning |
|---|---|
200 | Grouped failures, plus refused emails |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/failures" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/frames
Every house's outputs, browsable
What is actually being MADE on FLAM. Narrows server-side by house (organization id), tool (source_tool), age in days, and origin — made is anything the house developed, brought is what a director uploaded. The filter lists ride along in the same answer so the panel never needs a second round trip.
Responses
| Status | Meaning |
|---|---|
200 | Frames, plus the houses and tools that exist |
400 | Invalid input |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/frames" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/frames/{id}/file
The bytes of any house's frame (admin only)
assets.ts's streamAsset owner-gates on organization_id and answers 404 to everyone else — correct for a director, and fatal for an admin who has to look at the frame a customer is complaining about. Widening that route would put "or an admin" inside the DIRECTOR path, where a bug in the allowlist becomes a cross-house read for real users. This one is admin-only from its first line.
Responses
| Status | Meaning |
|---|---|
200 | The image bytes |
403 | Session email not in the admin allowlist |
404 | No such frame |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/frames/{id}/file" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/grant-tokens
Comp a token grant to a user (admin)
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
userId | string | yes | — |
tokens | integer | yes | — |
expiryDays | integer | null | no | — |
reason | string | no | — |
{
"userId": "string",
"tokens": 0,
"expiryDays": 0,
"reason": "string"
}Responses
| Status | Meaning |
|---|---|
200 | { ok, granted, tokens, expiresAt } |
400 | USER_REQUIRED | BAD_TOKENS (body includes max) |
403 | Session email not in the admin allowlist |
404 | USER_NOT_FOUND |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/grant-tokens" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userId":"string","tokens":0,"expiryDays":0,"reason":"string"}'POST /api/toolkit/admin/invite
Open the door for one email (admin)
Issues the PLATFORM-gate invitation: an invitations row plus permanent email-keyed studio access, so the invitee lands usable on signup. Idempotent — re-issuing an open door returns the same token and opens nothing twice. This is NOT the seat invitation (/api/toolkit/org/seats/invite).
Request body — application/json
| Field | Type | Required | Notes |
|---|---|---|---|
email | string (email) | yes | — |
{
"email": "noa@atelierberger.com"
}Responses
| Status | Meaning |
|---|---|
200 | The door is open for this address. |
400 | Bad address |
403 | Not an admin |
200 returns:
{
"email": "string",
"token": "string",
"url": "string",
"status": "issued",
"toolkitsOpened": 0
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/invite" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"noa@atelierberger.com"}'GET /api/toolkit/admin/invite-codes
List invitation codes (admin)
Newest first, live and dead together. usedCount is doors opened; joinedCount is how many of those addresses became an account — the conversion of one channel.
Responses
| Status | Meaning |
|---|---|
200 | Every code the house has minted. |
403 | Not an admin |
200 returns:
{
"codes": [
{
"id": "string",
"code": "FLAM-INNER-7QK4M2",
"label": "string",
"grantTokens": 0,
"maxUses": 0,
"usedCount": 0,
"joinedCount": 0,
"expiresAt": "string",
"revokedAt": "string",
"createdAt": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/invite-codes" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/invite-codes
Mint a shareable invitation code (admin)
Mints LABEL-XXXXXX — a vanity prefix plus six characters of Crockford base32. Redeeming it writes an ordinary invitation for the redeemer's address, so the invite-only gate is unchanged. maxUses is mandatory: a code that travels without a cap is an uncapped spend.
Request body — application/json
| Field | Type | Required | Notes |
|---|---|---|---|
label | string | yes | — |
maxUses | integer | yes | — |
grantTokens | integer | no | — |
expiresInDays | integer | no | — |
{
"label": "FLAM_INNER",
"maxUses": 25,
"grantTokens": 2000,
"expiresInDays": 30
}Responses
| Status | Meaning |
|---|---|
200 | The code, in full. Copy it now or read it back from the list later. |
400 | Missing a label, a cap, or a sane number. |
403 | Not an admin |
409 | The minted code collided. Ask again. |
200 returns:
{
"id": "string",
"code": "FLAM-INNER-7QK4M2",
"label": "string",
"grantTokens": 0,
"maxUses": 0,
"usedCount": 0,
"joinedCount": 0,
"expiresAt": "string",
"revokedAt": "string",
"createdAt": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/invite-codes" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label":"FLAM_INNER","maxUses":25,"grantTokens":2000,"expiresInDays":30}'POST /api/toolkit/admin/invite-codes/revoke
Kill an invitation code (admin)
Immediate and final — the code stops redeeming on the next request. Nobody who already came through it loses anything: their invitation and their tokens are theirs. POST, not DELETE: this house registers its destructive verbs as POST (see /admin/offerings/retire, whose DELETE answered 404 for a week).
Request body — application/json
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | — |
{
"id": "string"
}Responses
| Status | Meaning |
|---|---|
200 | The code is dead. |
400 | No id given |
403 | Not an admin |
404 | No such code |
200 returns:
{
"id": "string",
"code": "FLAM-INNER-7QK4M2",
"label": "string",
"grantTokens": 0,
"maxUses": 0,
"usedCount": 0,
"joinedCount": 0,
"expiresAt": "string",
"revokedAt": "string",
"createdAt": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/invite-codes/revoke" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id":"string"}'GET /api/toolkit/admin/invite-requests
List invitation requests (admin)
Responses
| Status | Meaning |
|---|---|
200 | Every request, newest first. |
403 | Not an admin |
200 returns:
{
"requests": [
{
"id": "string",
"name": "string",
"house": "string",
"email": "string",
"collection": "string",
"status": "string",
"createdAt": "string",
"answers": [
{
"question": null,
"answer": null
}
]
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/invite-requests" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/judge/rollup
The judge's verdicts, counted (admin only)
L1, the measurement layer — the same rows the quality browser lists, aggregated. Defect code × tool × rubric version × judge model, over time, for one house (?house=) or across the whole platform (omit it). No new table: this is a GROUP BY, and nothing is written. rubricVersion and judgeModel are grouping keys on every row including the trend, because two scores from different rubrics are not comparable and a line drawn through a rubric change is a lie — rubrics names the boundaries so the caller can break the series. Every rate ships with its denominator (frames of of). unreadable is excluded from every defect and flag rate and reported on its own: it is US failing to look, not a clean frame. outcomes joins generation_feedback so flagged can be checked against what the director then did, and caveats.deleted is NOT_YET_MEASURABLE because nothing in this API deletes an asset. Every query writes an admin_access_log row. Aggregates only — no imagery, no prompt, no email.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | house | string | no | An organization id. Omit for the whole platform. |
| query | tool | string | no | — |
| query | rubric | integer | no | Pin one rubric version. Omit to see every version, grouped. |
| query | model | string | no | Pin one judge model id. |
| query | bucket | "day" | "week" | "month" | no | Trend granularity. |
| query | since | integer | no | Days back. 0 or absent is all time. |
| query | limit | integer | no | Maximum cells per block. |
Responses
| Status | Meaning |
|---|---|
200 | Totals, defect cells with denominators, per tool, per house, the trend, the rubric boundaries and the outcome join |
400 | Invalid input |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/judge/rollup" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/judge/verdicts
Every judged frame, across every house (admin only)
The back office's half of the judge — the same rows /api/toolkit/judge/verdicts serves a director, plus the four things that route hides on purpose: the judge's own note, the model id, costUsd and latencyMs. Narrows server-side by house, tool, defect code, verdict band, director response, recipe arm and age. ?flagged=true is a synonym for ?band=flagged. unreadable comes back as its own count and is never folded into flagged — a wall of unreadable is US broken (a dead model id, a 404 on the frame, a timeout), not a wall of bad frames. Every query writes an admin_access_log row.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | band | "flagged" | "clean" | "unreadable" | "unjudged" | no | flagged · clean · unreadable · unjudged. Omit for every judged frame. |
| query | tool | string | no | — |
| query | house | string | no | An organization id. |
| query | defect | string | no | One of the twelve defect codes. |
| query | since | integer | no | Days back. 0 or absent is all time. |
| query | limit | integer | no | — |
Responses
| Status | Meaning |
|---|---|
200 | Judged frames, the three counts, the judge's spend, and the filter lists |
400 | Invalid input |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/judge/verdicts" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/judge/verdicts/{jobId}
One judged frame, whole (admin only)
What was asked for (her own params, scalars only — inputs is dropped because it holds base64 data URIs), what was fed in, what came out, what the judge said including its own sentence, and what the director did. feedback is all-null and recipe is the baseline arm until generation_feedback and prompt_recipes exist. Opening a frame writes an admin_access_log row: this read puts another house's unreleased imagery on a screen.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | jobId | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | The frame, its inputs, the verdict and the response |
403 | Session email not in the admin allowlist |
404 | No such job |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/judge/verdicts/{jobId}" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/kept-cost
Cost per KEPT frame, per house and per tool, over a window
Cost per generation flatters us — if she develops six and keeps one, that frame cost six renders. Money out of credit_ledger (joined on generation_jobs.hold_id, never metadata->>'tool') divided by the frames that SURVIVED. The denominator basis is spoke (kept + regenerated + deleted), never runs: a frame nobody has downloaded has not spoken, and silence is not rejection. Below minKept kept frames every rate on the row is null and why says so; below minCoverage the MEASURED rate is null while credit_ledger.real_cost_usd is still mostly zero, and only the MODELLED rate answers. deletedWired is false because no delete verb exists, so "fraction thrown away" is NOT YET MEASURABLE.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | since | integer | no | Window in days |
| query | house | string | no | One organization_id |
| query | tool | string | no | One tool slug |
Responses
| Status | Meaning |
|---|---|
200 | Cost per kept frame with its denominator, or a stated refusal |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/kept-cost" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/longstory/autonomy
Read Longstory's delegated authority
Admin-only, audited view of the active Progressive Autonomy policy and its append-only history. No saved policy means the safe recommend-only default.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | organizationId | string | no | — |
Responses
| Status | Meaning |
|---|---|
200 | The effective Longstory autonomy boundary. |
403 | Not a platform admin |
200 returns:
{
"source": "default",
"revision": 0,
"selection": {
"scopeType": "platform",
"organizationId": null,
"level": "observe",
"maximumAutomaticRisk": "low",
"maximumCanaryPercentage": 0,
"maximumProgressivePercentage": 0,
"maximumAutomaticChangesPerDay": 0,
"maximumAutomaticCostUsdPerDay": 0,
"minimumReplayPassRate": 0,
"minimumDistinctSubjects": 0,
"minimumDistinctOrganizations": 0
},
"policy": {},
"updatedAt": "string",
"history": [
{
"id": "string",
"revision": 0,
"status": "active",
"level": "observe",
"createdAt": "string",
"supersededAt": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/longstory/autonomy" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/longstory/autonomy
Create a new Longstory authority revision
Supersedes the current policy atomically. Frozen surfaces and evidence requirements cannot be weakened through this FLAM control plane.
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
scopeType | "platform" | "organization" | no | — |
organizationId | string | null | no | — |
level | "observe" | "recommend" | "shadow" | "experiment" | "progressive" | "autonomous" | yes | — |
maximumAutomaticRisk | "low" | "medium" | yes | — |
maximumCanaryPercentage | number | yes | — |
maximumProgressivePercentage | number | yes | — |
maximumAutomaticChangesPerDay | integer | yes | — |
maximumAutomaticCostUsdPerDay | number | yes | — |
minimumReplayPassRate | number | yes | — |
minimumDistinctSubjects | integer | yes | — |
minimumDistinctOrganizations | integer | yes | — |
{
"scopeType": "platform",
"organizationId": null,
"level": "observe",
"maximumAutomaticRisk": "low",
"maximumCanaryPercentage": 0,
"maximumProgressivePercentage": 0,
"maximumAutomaticChangesPerDay": 0,
"maximumAutomaticCostUsdPerDay": 0,
"minimumReplayPassRate": 0,
"minimumDistinctSubjects": 0,
"minimumDistinctOrganizations": 0
}Responses
| Status | Meaning |
|---|---|
200 | The new active policy revision. |
400 | Invalid policy selection |
403 | Not a platform admin |
200 returns:
{
"source": "default",
"revision": 0,
"selection": {
"scopeType": "platform",
"organizationId": null,
"level": "observe",
"maximumAutomaticRisk": "low",
"maximumCanaryPercentage": 0,
"maximumProgressivePercentage": 0,
"maximumAutomaticChangesPerDay": 0,
"maximumAutomaticCostUsdPerDay": 0,
"minimumReplayPassRate": 0,
"minimumDistinctSubjects": 0,
"minimumDistinctOrganizations": 0
},
"policy": {},
"updatedAt": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/longstory/autonomy" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scopeType":"platform","organizationId":null,"level":"observe","maximumAutomaticRisk":"low","maximumCanaryPercentage":0,"maximumProgressivePercentage":0,"maximumAutomaticChangesPerDay":0,"maximumAutomaticCostUsdPerDay":0,"minimumReplayPassRate":0,"minimumDistinctSubjects":0,"minimumDistinctOrganizations":0}'GET /api/toolkit/admin/memory
Observe living memory across every house
Admin-only, audited observatory for typed memories, Core projection state, outbox delivery, digestion, and retrieval. Statements and evidence ids are sensitive cross-tenant data, so every read is allowlist-gated and logged.
Responses
| Status | Meaning |
|---|---|
200 | The memory system's current state. |
401 | Signed out |
403 | Not a platform admin |
200 returns:
{
"totals": {
"memories": 0,
"active": 0,
"coreProjected": 0,
"pendingOutbox": 0,
"failedOutbox": 0,
"digestCostUsd": 0,
"retrievals24h": 0,
"injected24h": 0,
"digests24h": 0,
"failedDigests24h": 0,
"averageContextMemories24h": 0,
"latestDigestAt": "string",
"latestDigestModel": "string",
"populationCandidates": 0,
"observations24h": 0,
"observationCostUsd24h": 0
},
"digests": [
{
"id": "string",
"organizationName": "string",
"status": "string",
"model": "string",
"candidateCount": 0,
"contextMemoryCount": 0,
"inputTokens": 0,
"outputTokens": 0,
"costUsd": 0,
"error": "string",
"finishedAt": "string",
"createdAt": "string"
}
],
"memories": [
{
"id": "string",
"organizationId": "string",
"organizationName": "string",
"subjectType": "house",
"memoryClass": "fact",
"kind": "told",
"statement": "string",
"confidence": 0,
"status": "string",
"evidence": [
"string"
],
"coreMemoryId": "string",
"updatedAt": "string",
"replayEvidence": [
{
"id": "string",
"startMs": 0,
"focusMs": 0,
"endMs": 0,
"reason": "string",
"archiveState": "pending",
"providerAvailableUntil": "string",
"openUrl": "string",
"renders": [
null
]
}
]
}
],
"reflectionFindings": [
{
"id": "string",
"reflectionRunId": "string",
"kind": "population_lesson",
"status": "proposed",
"title": "string",
"claim": "string",
"recommendation": "string",
"evidenceRefs": [
"string"
],
"replayEvidence": [
{
"id": "string",
"startMs": 0,
"focusMs": 0,
"endMs": 0,
"reason": "string",
"archiveState": "pending",
"providerAvailableUntil": "string",
"openUrl": "string",
"renders": [
null
]
}
],
"confidence": 0,
"risk": "low",
"proposedReplay": {},
"proposedTicket": {},
"interventionId": "string",
"reviewedAt": "string",
"createdAt": "string"
}
],
"populationCandidates": [
{
"id": "string",
"signalCode": "string",
"status": "string",
"observations": 0,
"distinctOrganizations": 0,
"confidence": 0,
"lastObservedAt": "string",
"expiresAt": "string"
}
],
"behaviorObservations": [
{
"id": "string",
"organizationName": "string",
"provider": "string",
"method": "string",
"contextVersion": "string",
"contextDigest": "string",
"release": {},
"status": "string",
"summary": "string",
"outcome": "string",
"confidence": 0,
"findings": [
{}
],
"segments": [
{}
],
"costUsd": 0,
"observedAt": "string"
}
],
"proofs": [
{
"id": "string",
"lesson": "string",
"release": {
"name": "string",
"version": "string",
"exposurePercent": 0,
"exposedSubjects": 0,
"eligibleSubjects": 0,
"startedAt": "string"
},
"conclusion": "helpful",
"interventionStatus": "proposed",
"metrics": [
{
"label": "string",
"baseline": 0,
"treatment": 0,
"unit": "count",
"betterWhen": "higher"
}
],
"evidence": {
"strength": "weak",
"observations": 0,
"distinctOrganizations": 0,
"confounders": [
"string"
]
},
"replay": {
"status": "not_ready",
"passed": 0,
"total": 0,
"lastRunAt": "string"
},
"cost": {
"measurementStatus": "complete",
"unmeasuredComponents": [
"string"
],
"notes": [
"string"
],
"totalUsd": 0,
"items": [
{
"label": null,
"costUsd": null
}
]
},
"updatedAt": "string"
}
],
"chatScenarios": [
{
"id": "string",
"replayKey": "string",
"version": 0,
"title": "string",
"risk": "low",
"status": "pending",
"candidateRelease": {
"name": "string",
"version": "string"
},
"passed": 0,
"repetitions": 0,
"passRate": 0,
"failures": [
{
"repetition": 0,
"reasons": [
null
]
}
],
"durationMs": 0,
"firstActivityMs": 0,
"firstResponseMs": 0,
"costUsd": 0,
"finishedAt": "string"
}
],
"scenarioEvidence": [
{
"id": "string",
"suiteId": "string",
"suiteVersion": "string",
"replayKey": "string",
"replayVersion": 0,
"title": "string",
"layer": "deterministic_contract",
"passed": true,
"passRate": 0,
"minimumPassRate": 0,
"executions": 0,
"totalDurationMs": 0,
"totalCostUsd": 0,
"userCharge": [
{
"unit": "string",
"amount": 0
}
],
"agentReleases": [
{
"name": "string",
"version": "string",
"digest": "string"
}
],
"models": [
"string"
],
"featureFlags": [
"string"
],
"createdAt": "string",
"recordedAt": "string"
}
],
"valuePlans": [
{
"id": "string",
"interventionStatus": "proposed",
"headline": "string",
"userOutcome": "string",
"productOutcome": "string",
"primaryMetric": {
"label": "string",
"minimumEffect": 0,
"unit": "count",
"betterWhen": "higher"
},
"guardrails": [
{
"label": "string",
"maximumRegression": 0,
"unit": "count"
}
],
"evidence": {
"minimumControl": 0,
"minimumTreatment": 0,
"minimumOrganizations": 0,
"confidenceLevel": 0
},
"budget": {
"maximumTotalUsd": 0,
"maximumCostPerSuccessfulOutcomeUsd": 0
}
}
],
"retrievalReceipts": [
{
"id": "string",
"organizationName": "string",
"mode": "shadow",
"considered": 0,
"selected": 0,
"injected": 0,
"applied": 0,
"applicationEvidence": [
{
"source": "string",
"count": 0,
"averageConfidence": 0
}
],
"releaseName": "string",
"releaseVersion": "string",
"outcomeStatus": "succeeded",
"repeatedCorrection": true,
"error": "string",
"createdAt": "string"
}
],
"storyGraph": {
"schema_version": "1.0.0",
"id": "string",
"scope": {},
"view": "lesson_neighborhood",
"query": {},
"nodes": [
{}
],
"edges": [
{}
],
"list_equivalent": [
{}
],
"layout": {},
"truncated": true,
"next_cursor": "string",
"access_audit_id": "string",
"generated_at": "string"
}
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/memory" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/memory/{id}/review
Approve or reject one learned memory
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
decision | "approve" | "reject" | yes | — |
{
"decision": "approve"
}Responses
| Status | Meaning |
|---|---|
200 | The review was recorded. |
403 | Not a platform admin |
404 | Memory not found |
409 | Memory was already reviewed or its predecessor changed |
200 returns:
{
"ok": true,
"id": "string",
"status": "active"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/memory/{id}/review" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"decision":"approve"}'POST /api/toolkit/admin/memory/reflection-findings/{id}/decision
Accept or reject one evidence-backed reflection proposal
Accepting creates a review-only Longstory intervention. It does not expose a change, edit instructions, or grant serving authority.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
decision | "accept" | "reject" | yes | — |
{
"decision": "accept"
}Responses
| Status | Meaning |
|---|---|
200 | The decision was recorded. |
403 | Not a platform admin |
404 | Reflection finding not found |
409 | Reflection finding was already reviewed |
200 returns:
{
"ok": true,
"id": "string",
"status": "accepted",
"interventionId": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/memory/reflection-findings/{id}/decision" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"decision":"accept"}'GET /api/toolkit/admin/memory/replay-evidence/{id}
Open one audited session replay evidence moment
Admin-only capability-gated read. Every allowed or denied attempt is written to the immutable Longstory access ledger. Live PostHog is preferred while retained; verified R2 archives are the fallback.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
| query | mode | "auto" | "archive" | no | — |
Responses
| Status | Meaning |
|---|---|
200 | The verified raw replay archive when the live provider has expired |
302 | Redirect to the exact retained provider moment |
403 | Not a platform admin |
404 | Evidence not found |
410 | Neither the provider nor a verified archive is available |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/memory/replay-evidence/{id}" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/memory/replay-evidence/{id}/renders
Request an optional video rendering of replay evidence
Queues a derived MP4 or WebM from the canonical rrweb archive. Rendering is optional, audited, and inherits the evidence scope, expiry, and deletion policy.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
format | "mp4" | "webm" | no | — |
{
"format": "mp4"
}Responses
| Status | Meaning |
|---|---|
200 | Existing or newly queued render |
403 | Not a platform admin |
404 | Evidence archive not found |
409 | Canonical replay archive is not ready |
200 returns:
{
"id": "string",
"format": "mp4",
"state": "requested",
"failureCode": "string",
"requestedAt": "string",
"completedAt": "string",
"expiresAt": "string",
"downloadUrl": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/memory/replay-evidence/{id}/renders" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"format":"mp4"}'GET /api/toolkit/admin/memory/replay-evidence/{id}/renders/{renderId}
Download one audited replay evidence video
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| path | id | string | yes | — |
| path | renderId | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | Verified MP4 or WebM bytes |
403 | Not a platform admin |
404 | Ready render not found |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/memory/replay-evidence/{id}/renders/{renderId}" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/offerings
The catalogue, as the house sees it
Responses
| Status | Meaning |
|---|---|
200 | Offerings |
401 | No valid session |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/offerings" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/offerings
Create or update an offering (price, tier, previews)
Responses
| Status | Meaning |
|---|---|
200 | The saved offering |
400 | Invalid input |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/offerings" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/offerings/retire
Retire an offering from the board
Responses
| Status | Meaning |
|---|---|
200 | Retired |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/offerings/retire" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/onboarding
Who is setting up, and how far they got (admin)
Responses
| Status | Meaning |
|---|---|
200 | Everyone who has started setting up, latest movement first. |
403 | Not an admin |
200 returns:
{
"onboardings": [
{
"userId": "string",
"name": "string",
"email": "string",
"house": "string",
"completed": 0,
"total": 0,
"steps": [
"string"
],
"startedAt": "string",
"lastStepAt": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/onboarding" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/overview
The house at a glance — the first screen
Directors and houses, how many worked this week, the outstanding token float, what the engine is doing right now, what moved today against yesterday, and a fourteen-day strip. Money and tokens are separate fields and are never summed — there is deliberately no field holding the two added together.
Responses
| Status | Meaning |
|---|---|
200 | The house at a glance |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/overview" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/payments
Real transactions — subscriptions, top-ups, offerings, comps
Money reaches FLAM through three doors and the Money book only showed one. Comps are included and read as zero: hiding them makes the token float unexplainable, pricing them at anything else makes them look like revenue. amountUsd is currency and tokens is what that payment minted — separate fields, no total, because a top-up is money that bought tokens, not money and tokens.
Responses
| Status | Meaning |
|---|---|
200 | Transactions and standing subscriptions |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/payments" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/support
Tickets and feedback, with the screenshots directors sent
The one admin book that is not read out of Postgres. Tickets come from PostHog Support (the same inbox the studio's report sheet writes to) and feedback from REPORT_SURVEY's survey sent events. Every ticket's FULL first message is read per ticket rather than taken from the list, because the list truncates at ~500 characters and cuts the signed screenshot links mid-signature — a truncated signed link is a broken image, and the images are the point. screenshots are signed, expiring links on FLAM's own R2 that need no session, so they render as pictures. available: false means POSTHOG_PERSONAL_API_KEY is absent in this environment; it is a deployment fact and never an empty inbox.
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | limit | integer | no | How many tickets and how many feedback rows to return |
Responses
| Status | Meaning |
|---|---|
200 | Tickets with their pictures, and every rating given |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/support" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/tokenomics
What a token costs against what we charge, and the funnel
Three layers, never blended — the price book per tool AND per quality (tokens charged against credit_price_book.loaded_cost_usd), what actually ran from the jobs spine, and the conversion funnel. While costUnmeasured is true the worker still settles with a measured cost of zero, so every margin is MODELLED from the price book and the panel says so rather than printing a 100% margin.
Responses
| Status | Meaning |
|---|---|
200 | Unit economics, actuals and the funnel |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/tokenomics" \
-H "Authorization: Bearer $FLAM_API_KEY"DELETE /api/toolkit/admin/tool-items
Delete a curated item by id (admin)
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | id | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | { ok, deleted } |
400 | id required |
403 | Session email not in the admin allowlist |
Call it
curl -X DELETE "https://api.flam.fashion/api/toolkit/admin/tool-items?id=<id>" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/admin/tool-items
Curated items for one tool (admin)
Parameters
| In | Name | Type | Required | Notes |
|---|---|---|---|---|
| query | toolId | string | yes | — |
Responses
| Status | Meaning |
|---|---|
200 | { items } |
400 | toolId required |
403 | Session email not in the admin allowlist |
Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/tool-items?toolId=<toolId>" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/admin/tool-items
Append a curated item (admin)
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
toolId | string | yes | — |
category | string | yes | — |
contentEn | string | no | — |
titleHe | string | no | — |
labelHe | string | no | — |
imageUrl | string | no | — |
notesHe | string | no | — |
{
"toolId": "string",
"category": "string",
"contentEn": "string",
"titleHe": "string",
"labelHe": "string",
"imageUrl": "string",
"notesHe": "string"
}Responses
| Status | Meaning |
|---|---|
200 | { ok, id, sortOrder } |
400 | toolId and category required |
403 | Session email not in the admin allowlist |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/admin/tool-items" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"toolId":"string","category":"string","contentEn":"string","titleHe":"string","labelHe":"string","imageUrl":"string","notesHe":"string"}'GET /api/toolkit/admin/users
All users with balance + access (admin)
Responses
| Status | Meaning |
|---|---|
200 | User list |
403 | Session email not in the admin allowlist |
200 returns:
{
"users": [
{}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/admin/users" \
-H "Authorization: Bearer $FLAM_API_KEY"