Flip Kitchen API
You send a room.
We send back
a finished kitchen.
POST the walls, doors, windows and appliances of a room. SW Design comes back with complete layouts — every cabinet chosen, sized, placed against the right wall and stamped with a real cabinet code — in the same JSON shape you sent. Not a picture of a kitchen: the parts list of one.
It is the engine that runs flipkitchen.ca and the free designer at design.flipkitchen.ca — the same code path, not a demo copy.
What you can call
Four endpoints, one key.
/api/v1/design
Lay out a kitchen
A design document in, several complete layouts out — base, wall, tall and corner cabinets placed, fillers closed, appliances respected.
Scope design · 3 free runs a day per account, then
1 GH Coin a run. Free for members. Nothing is charged when the
engine cannot lay the room out.
/api/v1/agent
Talk to Emma
The design agent: she answers cabinet questions from our own knowledge base,
runs a whole layout when you pass a design, and hands your client the edits
she wants made as ops.
Scope agent · billed by model tokens —
1 GH Coin per 500,000, summed over the rounds a turn needs. A layout
she runs bills like the endpoint above.
/api/v1/voice
Talk to her out loud
The same Emma, spoken both ways. You get a short-lived token; your client opens the live audio session with it and runs the tools she calls. Her persona and tool contract are locked into that token on our side.
Scope agent · sold by the clock:
1 GH Coin per 2-minute block, charged when the token is issued.
/api/photo-enhance
Photograph it
One rendered still in, one photographic interior out — grading, material micro-texture and a believable window view, with the geometry left alone.
Scope render · 1 GH Coin an image.
Ask us
about the design-aware Render Studio path.
Quickstart
From nothing to a kitchen in three steps.
-
Step 1
Mint a key
Sign in, open Settings → API keys, name it after the thing that will use it and tick the scopes it needs. The key is shown once — store it in your server's secret manager.
-
Step 2
Get a room to send
Fastest path: draw one in the free designer at design.flipkitchen.ca and press ⬇ Download JSON. That file is a valid design document — send it straight back to us.
-
Step 3
POST it
Every variant that comes back is a whole design document again, so you can hand it to your own renderer, save it, or send it back in for another pass.
# Two walls, nothing in the room yet. The key lives in the environment — # never in the file you commit. curl -X POST https://www.flipkitchen.ca/api/v1/design \ -H "Authorization: Bearer $FLIP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "design": { "name": "Demo kitchen", "layoutType": "DIY", "selectedDoorColor": "BW", "customWalls": [ { "id": "cw1", "x1": 0, "z1": 0, "x2": 144, "z2": 0, "height": 96, "thickness": 3, "designSide": "right" }, { "id": "cw2", "x1": 144, "z1": 0, "x2": 144, "z2": 120, "height": 96, "thickness": 3, "designSide": "left" } ], "openings": { "windows": [], "doors": [], "cutouts": [] }, "kitchenElements": [ { "type": "sink", "wall": "cw1", "position": 60, "width": 33 }, { "type": "fridge","wall": "cw2", "position": 100, "width": 36 } ], "cabinets": [] } }'
// 200 — abridged. Each variant is a full design document. { "ok": true, "schema": "fk.design/1", "count": 4, "failed": 0, "variants": [ { "name": "Demo kitchen — SW 1", "customWalls": [ /* what you sent, normalised */ ], "cabinets": [ { "sku": "SB36", "wall": "cw1", "position": 43.5, "width": 36 }, { "sku": "B18", "wall": "cw1", "position": 79.5, "width": 18 } ] } ], "coin": { "charged": 1, "balance": 41 }, "freeLeft": 0, "freePerDay": 3, "member": false }
Cabinet codes, positions and counts above are illustrative — the engine picks them from the room you send.
The design document
One shape, both directions.
The thing you POST and the thing you get back are the same kind of object — the
document our designer saves. Everything is in inches, in plan
view: x runs left-right, z runs
up-down on the drawing.
What the engine reads
| Field | What it is |
|---|---|
| customWalls | Required. The room. Each wall: id, x1 z1 x2 z2, height, thickness, and designSide — "right", "left" or "double": which side of the line the kitchen is on. Up to 40 walls. |
| openings | { windows, doors, cutouts }. Each entry sits on a wall: wall, position (inches along it), width. Cabinets are kept clear of them. |
| kitchenElements | Appliances and fixtures — sink, range, fridge, dishwasher and friends — as { type, wall, position, width }. These anchor the layout: the engine builds around them. |
| cabinets | Cabinets that already exist. Send [] for a fresh layout. |
| island / pantry | Optional blocks, when the room has them. |
| selectedDoorColor | Door colour code for the returned cabinets (default "BW"). |
| wallCabHeight | "auto" or a height in inches. |
| noWallCabs | true to lay out base cabinets only. |
| layoutType | "DIY" for a room you drew yourself — the normal case. |
Anything else in the document is carried along untouched. Unknown fields are never an error — that is how the schema grows without breaking you.
Request options
| Body field | What it does |
|---|---|
| design | Required. The design document. |
| designWallIds | Array of wall ids. Lay out only these walls and leave the rest of the room alone. |
| nameStamp | Up to 40 characters appended to each variant's name — handy for tracing a run. |
Response
| Field | What it is |
|---|---|
| ok | false with an error when the engine could not lay this room out. Not billed. |
| variants | The layouts. Each is a full design document. |
| count / failed | How many came back, and how many of those the engine itself flagged. |
| input | The engine input we derived from your document — useful when you are debugging why a room came out the way it did. |
| coin | { charged, balance } when a coin was spent. |
| freeLeft / freePerDay | What is left of today's free runs. |
The agent
Emma answers — and your app is her hands.
Two things happen on our side: she looks things up in the cabinet knowledge base,
and she runs SW Design when you pass a design.
Everything else she wants done comes back as ops for
your client to execute — so an app with a canvas gets an assistant that
edits the drawing, and a plain server still gets answers and whole layouts.
// Turn 1 — tell her what your client can do. const r = await fetch("https://www.flipkitchen.ca/api/v1/agent", { method: "POST", headers: { "Authorization": `Bearer ${process.env.FLIP_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: "Design my kitchen" }], design, // lets her run a layout capabilities: ["designer"], // tools YOUR client can run canvas: "empty plan, two walls drawn", lang: "en" }) }); const { reply, ops, assistant_message, variants } = await r.json();
// Turn 2 — run her ops, report back, call again. const results = ops.map(op => ({ role: "tool", tool_call_id: op.id, content: JSON.stringify(runInYourApp(op.name, op.args)) })); await fetch("https://www.flipkitchen.ca/api/v1/agent", { method: "POST", headers: { "Authorization": `Bearer ${process.env.FLIP_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ messages: [...history, assistant_message, ...results], design, capabilities: ["designer"] }) });
capabilities is "draw" (a 2D
plan) or "designer" (the full editor). Send none and
she is never offered a tool your client cannot run.
Every reply carries usage — the model tokens this turn
cost, summed over the rounds she needed. Nothing is charged for them today;
when the agent leaves preview it is priced on exactly that number, and your
key's running total is on the
API keys page.
Keys, limits, errors
The rules, in full.
What a call costs
Everything is paid in GH Coins off the account the key belongs to — the same wallet, and the same prices, as the buttons in our own app. API calls have no free allowance: the free daily runs and free voice minutes belong to people using flipkitchen.ca, not to a product running on our engine. Top up at flipkitchen.ca/topup.
| Call | Unit | Price |
|---|---|---|
| /api/v1/design | one run | 1 GH Coin |
| /api/v1/agent | model tokens (prompt + completion, all rounds) | 1 GH Coin per 500,000 tokens — a fraction of a coin carries over to your next call, so a short turn is never rounded up |
| /api/v1/voice | a 2-minute block | 1 GH Coin, charged when the token is issued and refunded if it could not be minted |
| /api/photo-enhance | one image | 1 GH Coin |
Every agent reply carries usage and
billing — what the turn cost in tokens, what was charged,
and the balance left — so you can meter your own users without guessing. Per-key
totals are on your API keys page.
Two kinds of key
A key on a server can stay secret. A key in a web page cannot — so there are two, and the browser one is built to survive being read.
fk_live_ · server key
- ✓Lives in your backend's environment. Works with no
Origin— server to server. - ✓Never put one in browser JavaScript, a mobile binary or a public repository.
fk_pub_ · browser key
- ✓Domain-locked. It only works from the domains you listed when you made it; a request with no
OriginorRefererat all is refused, which is what stops someone replaying it from a script. - ✓Daily ceiling. You set the most it may spend in a day (20 GH Coins by default), so a copied key costs you a day, not a wallet.
- ✓Paste it straight into your own page — that is what it is for.
What a key is
- ✓A key is the account that made it. It spends that account's GH Coins under the same rules the buttons in our app follow — there is no separate API price list.
- ✓Scopes
design,render,agent. A key is refused on a route outside its scopes. - ✓Five keys per account. Revoke one and it stops working immediately; usage per key is kept so you can see which integration spent what.
- ✓A key can never reach your saved designs, projects or account settings — only the three endpoints above.
Browser calls. The API refuses cross-origin requests from hosts it does not know, and a key in browser JavaScript is a key you have given away. Call us from your server. If you need a browser on your own domain to talk to us directly, ask us to allow-list it.
Rate and quota
| /api/v1/design | 200 requests an hour per address. Three free runs a day per account, then 1 GH Coin each; members run it freely. |
| /api/v1/agent | 120 requests an hour per account (20 an hour without a key). |
| /api/v1/voice | 60 blocks an hour per address. |
| No key at all | Three layouts a day per address, then 402. That is what the free public designer runs on. |
| Body size | 4 MB for a design, 16 KB for key management. |
Errors
| Status | code | What happened |
|---|---|---|
| 400 | bad_json · bad_design · no_walls · bad_wall · no_messages | The body did not parse, or the document has no usable walls. Fix and resend — nothing was billed. |
| 401 | signin_required | No key, an unknown key, or a key outside its scope. |
| 402 | — | Out of GH Coins, or the free daily allowance is used up. Top up and retry. |
| 403 | api_key_domain · api_key_scope | A browser key used from a domain it was not issued for (or with no origin at all), a key outside its scopes, or a browser request from an origin we do not know. |
| 402 | api_key_day_cap | A browser key hit its daily coin ceiling. It resets tomorrow, or raise it in settings. |
| 429 | — | Rate limited; retry_after_s says for how long. |
| 503 | — | Our usage counters are down. We fail closed rather than give work away — retry in a minute. |
Beyond the API
Put the designer itself in your product.
design.flipkitchen.ca is the free, public build of our designer: draw walls, drag cabinets in, see it in 3D, download the JSON. No account, no key, nothing to install.
The same designer can run on your domain with your key: make a
browser key for design.yourbrand.com,
and every layout your visitors generate there is billed to your account — no
accounts for them, nothing to install, and you keep the daily ceiling.
Branding, your own catalogue and the rest of the embed programme is a conversation rather than a signup form. Tell us what you are building.
Versioning promise
- ✓A design document carries
"schema": "fk.design/1". Read it, and migrate forward when it changes. - ✓Inside a version, fields are only ever added — never renamed, never removed. Ignore what you do not recognise.
- ✓New behaviour arrives behind a new field, off by default.
- ✓Breaking changes get a new path (
/api/v2/…), and v1 keeps working.