The API
The house is callable from outside the studio — an HTTP API described in OpenAPI, an organization key your own systems hold, and an MCP server so an agent can commission work directly. Outbound webhooks are designed and not yet built.
One thing here does not answer yet
Everything under what is live is in the product today and callable now. Outbound webhooks are a settled design and nothing more. They are written down because the shape will not move much, not because you can subscribe to them. If you are costing an integration this week, cost everything above that section.
A develop you start from your own system is the same develop a director starts in the studio. Same routes, same ledger, same frames. An agent gets the same house a director gets, through a key of its own.
What is live
The described surface
| Route | What it gives you |
|---|---|
GET /openapi.json | The whole surface, as OpenAPI 3.1 |
GET /docs | The same document, browsable |
One URL describes every route, which is the door to use if you are an agent rather than a person. Most of it comes from the routes themselves. The multipart and socket routes are contracted by hand and merged into the same document, because serving only the automatic half would show a third of the API and look complete.
Routes also carry a runnable request in the Bruno collection in the repo, and CI refuses any route added from here on without one. The surface predates that rule, so a recorded list of older routes is still catching up, and it may only shrink. A route nobody can call is a promise nobody can keep.
Signing in
Two doors, and they arrive at the same place. A browser session, or an organization key on
the Authorization header. Both resolve to the same house, the same role and the same
403s, so a route behaves identically whether a person or an agent asked.
Organization keys
A key belongs to the house rather than to a person, so taking a seat off the team cannot orphan a running integration.
| Route | What it does |
|---|---|
POST /api/keys | Mint one. The raw key is returned here and nowhere else |
GET /api/keys | Every key the house has minted, revoked ones included |
DELETE /api/keys/:id | Revoke, effective on the very next request |
Send it as Authorization: Bearer flam_sk_…. Only the hash is stored, so a key that is
lost is re-minted, never recovered. Revoking is a row update read on every call, so there
is no cache to wait out.
A key carries one of two roles. viewer reads and spends nothing. member may spend the
house's tokens. Neither reaches money: no key starts a subscription, buys a top-up or a
model, or invites a seat, and no key can mint or revoke another key. The worst a leaked key
does is spend tokens you had already bought, so revoke it and carry on.
An agent's own door
POST /api/mcp is an MCP server: JSON-RPC over one HTTP request, authed by the key above.
It is not a wrapper around the routes below. It is the six things an agent needs to
commission work, in shapes an agent can hold — an asset id where a person would upload a
file, one call where a person would make four.
| Tool | What it does |
|---|---|
list_capabilities | What the house can develop, and what each costs |
check_balance | Spendable tokens, and what is on hold |
list_assets | The house's frames, filterable |
start_develop | Commission a develop. Needs a member key |
get_develop | One develop's state and result |
list_develops | Recent develops, newest first |
It answers with a single JSON object rather than a stream, so an agent starts a develop and
then watches it with get_develop, or over the socket below. There are no MCP resources,
no prompts and no server-initiated sampling.
Submitting a develop
Long work does not block. Submit, take the id, listen.
POST /api/toolkit/jobs/submit accepts a multipart form and answers 202 with
{ jobId, status: "queued" }.
| Field | What it does |
|---|---|
tool | Which develop to run. An unrecognised one comes back UNKNOWN_TOOL. |
| images | At least one, 12 MB each at most. |
quality | 1k, 2k or 4k. This is what the frame costs. See quality and cost. |
idempotencyKey | Yours to choose. Send the same key twice and you get the job you already have, not a second charge. |
runId | Optional. Frames sharing one group together as a single run in the Darkroom. |
Tokens are held before anything is queued. If the balance will not cover the work you get
402 with need and spendable, and nothing is created or charged. The hold settles when
the frame lands and is released when it fails, so an interrupted develop costs nothing.
Errors are named rather than narrated: NO_IMAGE, IMAGE_TOO_LARGE, BAD_FORM,
INSUFFICIENT_TOKENS, ENQUEUE_FAILED. Switch on the string.
Listening for the result
GET /api/toolkit/jobs/ws upgrades to a WebSocket carrying every state change on your
organization's develops. It is addressed per organization rather than per person, so a run
one seat started is watchable from another. An idle client makes no requests and still
knows the moment a frame lands.
If your client was away, GET /api/toolkit/jobs?since= replays what it missed.
POST /api/toolkit/jobs/:id/cancel holds a run; /duplicate runs it again.
How a purchase becomes yours
Worth knowing before you build anything on top of a checkout. A browser returning from payment grants nothing. The payment provider's signed delivery is the only thing that writes an entitlement, verified and deduplicated on arrival. Read the entitlement, never the redirect.
Outbound webhooks
One thing here is designed and not written, and this is it. Nothing dispatches today. It is described because the shape is settled and you may want to plan around it.
Signed and retried, so your system learns a develop has settled without asking. The events:
| Event | When |
|---|---|
run.started | A run was accepted and has capacity |
run.frame.settled | One frame finished and was charged |
run.settled | Every frame in the run finished |
run.failed | The run ended without completing |
purchase.granted | A payment completed and something pre-made became yours |
Until these exist, the socket above is the way to be told, and it is the same way the studio itself is told.
When they land, a change to a public capability becomes one change across five places at once: the route, its Bruno request, its OpenAPI description, its webhook event and its MCP tool.
Related
- What a run is: the Darkroom.
- What a frame costs: quality and cost.
- The changelog entry behind this page →