HouseKraft API
Run HouseKraft's interior design tools from your own code: repaint walls, lay new floors, stage or empty rooms (one at a time or up to 50 in one job), change the light or the weather, try many colours, floors and themes on a room at once, and turn photos into 360° panoramas and walkthrough videos. Every tool runs the website's own code, so results, prices and your Library are exactly as on the website.
Accounts with an active paid plan, or that have bought credits.
The tools
Getting started
Five steps from a photo to a result. Responses are formatted JSON, readable as they are.
- Make a key
Sign in and make an API key on the Developers page: https://app.housekraft.co/developers. It is shown once; keep it secret.
export HOUSEKRAFT_KEY=hk_live_… - Check it works
Shows your credits and plan.
curl -s https://app.housekraft.co/api/v1/account -H "Authorization: Bearer $HOUSEKRAFT_KEY" - Upload a photo
The answer has asset.id. Or skip this and put an https:// image URL in the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run a tool
Quick tools answer with the finished result; slower ones answer with a job to check.
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "color_id": "sage green" }' - Get the result
If the answer says "done", the link to the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Authentication
Send your key in the Authorization header on every request:
Authorization: Bearer hk_live_…Every request except help, the tool list, pricing, options and health needs your API key. Keys are made and revoked on the Developers page. A revoked key stops working at once, and so do the file links it got; revoked keys stay listed there.
| Key | Starts with | For |
|---|---|---|
| API key | hk_live_ | Your own code: this API. |
| MCP key | hk_mcp_ | AI assistants, through the MCP server. It does not work on the API. |
Running a tool
POST /api/v1/tools/{tool}/run with a JSON body of the tool's inputs. See each tool's help for its inputs.
Quick tools answer with the result
walls, flooring, lighting, weather, stage, unstage, dollhouse wait for the result (up to 50 s) and answer 200 with the finished job. If it takes longer they answer 202 with check.url.
Slower tools answer with a job
material, furniture, pov, editor, panorama, walkthrough, walkthrough_multi, bulk_stage, bulk_unstage, enmasse answer 202 at once with the job. Fetch GET /api/v1/jobs/{id} until status is "done".
Choose for yourself
Add "wait": true to any run to wait up to 50 s, or "wait": false to get the job id straight away.
Job status
| status | Meaning |
|---|---|
running | Still working. Fetch again after poll_after_seconds. |
done | Finished. The link to the file is at job.output.url (for tools with several results, in job.outputs). job.tips may suggest how to do even better. |
failed | Did not finish. job.error.message says why, and the credits are refunded (job.credits.refunded = true). |
A job carries on if your connection drops. GET /api/v1/jobs lists your recent jobs.
Tools with several results
bulk_stage, bulk_unstage, enmasse: These give one result per photo (bulk) or per reference (En Masse). job.output is null; instead job.outputs lists every result in order, each with its own status, output (with url) and error, and job.progress counts them (total, done, failed). En Masse outputs also say which of your references each is for (reference, its place in your list) and its mode.
| outputs[].status | Meaning |
|---|---|
waiting | Queued: bulk photos go to the model a few at a time. |
running | Being worked on. |
done | Ready at output.url. |
failed | Could not be finished; its credits are given back. error says why. |
A bulk job where some photos failed still ends "done", with those photos marked failed. It ends "failed" only if no photo could be finished. Bulk results appear in outputs one by one while the job runs; En Masse results all arrive at the end.
Every result's link, one per line:
curl -s https://app.housekraft.co/api/v1/jobs/<job id> -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
| jq -r '.job.outputs[] | "\(.index) \(.status) \(.output.url // .error)"'Result files
Every url and thumbnail_url (job outputs, assets) is a HouseKraft file link. It opens the file directly, with no login and no key, for 60 minutes, so you can show it in a page or download it. Keep links private: anyone with one can open it until it expires.
An expired link answers 410 link_expired: fetch the job or asset again for a fresh one. Revoking the key that got a link stops it at once.
Or fetch any of your files with your key, no link needed:
curl -s https://app.housekraft.co/api/v1/assets/<asset id>/file -H "Authorization: Bearer $HOUSEKRAFT_KEY" -o result.jpgTo keep a file, download it: links expire, the file stays in your Library.
When a run needs more
If a run is missing what makes it worth running (a style to stage in, a colour, a camera move, a video length…), nothing runs and nothing is charged: the answer is 400 needs_more, with the question to ask (message) and good answers (suggestions).
{
"error": {
"code": "needs_more",
"message": "How long should the video be (1–15 seconds)? The length sets the price.",
"suggestions": [
"4 seconds: …",
"6 seconds: …",
"10 seconds: …"
],
"what_to_do": "Ask the user, then send the run again with it. Nothing ran and nothing was charged."
}
}A finished job can carry tips: ways the same run gets better (a brand paint from the catalogue, a photo of the exact piece…).
Images
Each image field takes an asset id or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
Upload a file
curl -s -X POST https://app.housekraft.co/api/v1/uploads \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-F image=@room.jpgUpload from a URL
curl -s -X POST https://app.housekraft.co/api/v1/uploads \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/room.jpg"}'Both answer with asset.id. You can also put an https:// URL straight into
any image field of a run.
Several photos, or a .zip
Tools that take several photos (images) or references take a list of asset ids. Upload the photos first: one per request, several in one request (repeat the image field), or a .zip of up to 50 photos. Several files or a .zip answer with assets[] (each with its file name) and skipped[] (files that were left out, and why). Sending the same id twice counts as one photo.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-F image=@listing.zipReuse a result
A finished job's output.id (or outputs[].output.id) is an asset id: send it to another tool to chain edits.
Catalogues
HouseKraft's catalogues, as on the website: browse them, then use an item in a run, such as staging a room in a theme or laying a brand's tile.
| Catalogue | What it holds |
|---|---|
themes | Interior design themes: a whole style to stage or restyle a room in. |
materials | The material library: stones, woods, metals, fabrics and more, for surfaces. |
paints | Paint colours from paint brands, each with its colour code. |
flooring | Tiles and floors from flooring brands. |
furniture | Furniture from furniture brands. |
Search a catalogue
curl -s "https://app.housekraft.co/api/v1/catalog/flooring?search=beige%20marble&per_page=5" \
-H "Authorization: Bearer $HOUSEKRAFT_KEY"search matches names, brands and categories ("Kajaria beige", "japandi", "oak"). groups lists the brands (or categories) to narrow by: pass one as ?group=…. Pages hold up to 50 items; browsing is free.
curl -s https://app.housekraft.co/api/v1/catalog/paints/groups \
-H "Authorization: Bearer $HOUSEKRAFT_KEY"Use an item
Each item has "use": for each tool, the inputs to add to that tool's run, besides the room photo, to apply the item. A theme has use.stage, a tile use.flooring, a paint use.walls, a material use.material, a furniture piece use.furniture. For enmasse, add use.enmasse.reference to the references list.
An item (example)
{
"name": "Japandi Calm",
"details": "Japandi",
"image": "https://media.housekraft.co/…/japandi-calm.jpg",
"thumbnail": "https://media.housekraft.co/…/japandi-calm.jpg",
"use": {
"stage": {
"staging_mode": "reference",
"reference_image": "https://media.housekraft.co/…/japandi-calm.jpg",
"theme_name": "Japandi Calm"
},
"bulk_stage": {
"reference_image": "https://media.housekraft.co/…/japandi-calm.jpg"
},
"dollhouse": {
"reference_image": "https://media.housekraft.co/…/japandi-calm.jpg",
"theme_name": "Japandi Calm"
},
"enmasse": {
"reference": {
"mode": "themes",
"image": "https://media.housekraft.co/…/japandi-calm.jpg"
}
}
}
}Stage a room in that theme
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"staging_mode": "reference",
"reference_image": "https://media.housekraft.co/…/japandi-calm.jpg",
"theme_name": "Japandi Calm"
}'By name or id, straight in a tool
Or skip the lookup: give a catalogue item to a tool by its name or its id. A name that matches several items answers 400 choose_one with the options; one that matches nothing, not_in_catalog. Nothing is charged for either.
| Tool | Input | Catalogue | Example |
|---|---|---|---|
| stage, bulk_stage, dollhouse | theme | themes | Japandi Calm |
| walls | paint | paints | Sage Whisper |
| flooring | flooring_product | flooring | Kajaria Beige Marble |
| material | library_material | materials | Calacatta Gold Marble |
| furniture | furniture_product | furniture | Ekenas Armchair |
Paint the walls a brand colour, by name
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"paint": "Sage Whisper"
}'A theme from a category
stage, bulk_stage and dollhouse also take theme_category (a category from GET /api/v1/catalog/themes/groups): the first theme in it is used, or a random one with "random_theme": true.
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<photo 1>", "<photo 2>"], "theme_category": "Scandinavian"}'Many at once, for En Masse
En Masse references can pick many items at once: {"pick": "paints" | "flooring" | "themes", "count": N} with an optional "group" (a brand or category), "search" (e.g. "green") and "random": true. If the catalogue has fewer than N, the answer is needs_more with how many there are.
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{"pick": "paints", "group": "Asian Paints", "count": 5},
{"pick": "flooring", "group": "Kajaria", "count": 3},
{"pick": "themes", "group": "Japandi", "count": 2}
]
}'Chaining tools
Every result has an output.id. Send it as the image of the next run.
Restyle a furnished room: unstage, then stage the empty result
curl -s -X POST https://app.housekraft.co/api/v1/tools/unstage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id from POST /api/v1/uploads>"}'curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<output.id from the unstage job>",
"prompt": "Japandi, warm wood and linen"
}'New walls and a new floor
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "warm white"
}'curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<output.id from the walls job>",
"flooring_id": "oak_hardwood"
}'Restage a whole furnished listing: bulk unstage, then bulk stage the results
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_unstage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<photo 1>", "<photo 2>", "<photo 3>"]}'curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": ["<outputs[0].output.id>", "<outputs[1].output.id>", "<outputs[2].output.id>"],
"prompt": "Scandinavian, light oak"
}'Image checks
- Image check: whether this photo suits this tool (the website's own check). A rejection is 422 image_check_failed with the reason and a fix. Add "override_check": true to run anyway.
- Misuse check: the photo must show a room, building or plan, and the request must be about the space, not people. A refusal is 422 request_not_allowed and cannot be overridden.
- With several photos, every photo is checked before anything starts. If any fails, nothing runs and the error lists each photo that failed (photos[]), so you can replace just those.
- Both checks are free: nothing is charged for a rejected request.
Credits
- Each tool is priced exactly as on the website, from the same price table and rules.
- Most tools take their credits when the run starts. If the run fails, the credits are refunded automatically and the job shows credits.refunded = true.
- Bulk Staging and Bulk Unstaging take credits photo by photo, as each photo starts, and give back the credits of any photo that fails. job.credits shows what was taken (charged) and given back (refunded_credits).
- En Masse takes its credits only once its results are ready, so a run that fails costs nothing.
- Free: requests rejected before running (invalid inputs, the image check, the misuse check, too many running jobs), uploads, option lists, help, and checking job status as often as you like.
- A run you push past the image check with "override_check": true is charged like any other run.
- Credits come from your account balance: your plan's credits plus any credit packs. API and website use the same balance.
Check your balance
curl -s https://app.housekraft.co/api/v1/account -H "Authorization: Bearer $HOUSEKRAFT_KEY"Errors
Every error has the same shape. code is stable, for programs; message is for people.
{
"error": {
"code": "image_check_failed",
"message": "The walls are hidden behind furniture.",
"fix": "Use a photo where more of the walls are visible.",
"reasons": [
"walls mostly covered"
],
"can_override": true,
"hint": "Send the same request with \"override_check\": true to run it anyway."
}
}| HTTP | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | A field is missing, unknown, or the wrong type. The message names the field. | Fix the field and send again. Nothing was charged. |
| 400 | needs_more | The run is missing what the tool needs to be worth running (a style, a colour, a camera move, a video length…). message is the question to ask; suggestions lists good answers. | Ask the user, then send the run again with the answer. Nothing ran and nothing was charged. |
| 400 | choose_one | A catalogue name matches several items. options lists them (id, name, details). | Ask which one, then send its name or id. Nothing was charged. |
| 400 | not_in_catalog | No catalogue item (or category) matches that name. | Search the catalogue (GET /api/v1/catalog/{catalog}?search=…) and use a name or id from it. |
| 401 | missing_api_key | No key in the Authorization header. | Send Authorization: Bearer hk_live_… |
| 401 | invalid_api_key | The key is wrong or was revoked. | Check the key, or make a new one on the Developers page. |
| 403 | wrong_key_kind | An MCP key (hk_mcp_…) was sent to the API. MCP keys work only through the MCP server. | Use an API key (hk_live_…) from the Developers page. |
| 402 | insufficient_credits | Not enough credits for this run. | Buy credits or upgrade your plan, then try again. |
| 403 | paid_plan_required | The account has no active paid plan and has never bought credits. | Subscribe or buy a credit pack. |
| 403 | account_suspended | The account is suspended. | Contact HouseKraft support. |
| 410 | link_expired | A file link is older than 60 minutes. | Fetch the job or asset again for a fresh link. |
| 409 | bulk_job_running | You already have a bulk job running (on the website or the API). | Wait for it to finish, then start the next one. |
| 404 | unknown_tool | No tool with that id. | See GET /api/v1/tools for the tool ids. |
| 404 | image_not_found | An asset id is not in your account, or it has expired. | Upload the photo again with POST /api/v1/uploads. |
| 404 | image_unavailable | The asset exists but its file could not be read. | Upload the photo again. |
| 422 | image_check_failed | The image check thinks this photo does not suit this tool. The answer has the reason (message, reasons) and a suggested fix. | Use a better photo, or send the same request with "override_check": true to run it anyway. Nothing was charged. |
| 422 | request_not_allowed | The photo is not of a room, building or plan, or the request is about people rather than the space. | This cannot be overridden. Nothing was charged. |
| 429 | rate_limited | Too many requests (120 a minute) or runs (10 a minute) with this key. | Wait the number of seconds in the Retry-After header. |
| 429 | too_many_running_jobs | You already have 3 jobs running. | Wait for one to finish, then start the next. |
| 429 | busy | The API is at capacity right now. | Try again in about 30 seconds. |
| 502 | tool_error | The tool could not prepare this request. | Try again. Nothing was charged. |
| 502 | provider_error | The image model could not be reached. | Try again. If credits were taken, they were refunded (the message says so). |
| 503 | check_unavailable | The safety check could not run just now. | Try again in a minute. Nothing was charged. |
| 503 | start_timeout | The run took too long to start. | Try again. Nothing was charged. |
All endpoints
| Method | Path | Key | What it does |
|---|---|---|---|
| GET | /api/v1/help |
No | This guide. |
| GET | /api/v1/tools |
No | Every tool, its inputs and its price. |
| GET | /api/v1/tools/pricing |
No | What each run costs, and how the price is worked out. |
| GET | /api/v1/tools/{tool} |
No | One tool's inputs. |
| GET | /api/v1/tools/{tool}/help |
No | One tool in full: inputs, examples in curl, Python and JavaScript, responses, tips. |
| GET | /api/v1/tools/{tool}/options |
No | The website's option list for a tool (colours, floorings, times of day…). |
| POST | /api/v1/uploads |
Yes | Upload a photo (form field "image", or JSON {"url": "https://…"}): answers with asset.id. Several files, or a .zip of up to 50 photos, answer with assets[]. |
| POST | /api/v1/tools/{tool}/run |
Yes | Run a tool. Answers with the job. |
| GET | /api/v1/jobs/{id} |
Yes | One job. Fetch it until status is "done" or "failed". |
| GET | /api/v1/jobs |
Yes | Your recent jobs (?limit=20&offset=0). |
| GET | /api/v1/assets |
Yes | Your Library (?type=generated|uploaded|all&limit=20&offset=0). |
| GET | /api/v1/assets/{id} |
Yes | One Library item. |
| GET | /api/v1/assets/{id}/file |
Yes | The file itself, with your key (no link needed). |
| GET | /api/v1/files/{id}?t=… |
No | A file link from a job or asset: opens without a key for 60 minutes. |
| GET | /api/v1/account |
Yes | Your credits and plan. |
| GET | /api/v1/catalog |
Yes | The catalogues: themes, materials, and paint, flooring and furniture brands. |
| GET | /api/v1/catalog/{catalog} |
Yes | One page of a catalogue (?search=…&group=…&page=0&per_page=10). Each item says what to send to which tool to use it. |
| GET | /api/v1/catalog/{catalog}/groups |
Yes | The brands or categories a catalogue can be narrowed by (for ?group=…). |
| POST | /api/v1/mcp |
Yes | The MCP server, for AI assistants (Claude Code, Cursor, VS Code, Claude Desktop). See "Use with AI assistants". |
| GET | /api/v1/health |
No | Is the API up. |
Full code
Upload a photo, run Wall Paint, and wait for the result. Set HOUSEKRAFT_KEY first.
Every tool below has the same code for its own inputs.
# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Wall Paint
job = call("POST", "/api/v1/tools/walls/run", json={
"image": photo,
"color_id": "sage green",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Wall Paint
let { job } = await call("POST", "/api/v1/tools/walls/run", {
"image": photo,
"color_id": "sage green",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
Use with AI assistants (MCP)
The same tools for AI assistants. Add HouseKraft to Claude Code, Cursor, VS Code or Claude Desktop, then ask in plain words ("stage this living room in Scandinavian style"): the assistant picks the tool, runs it on your account and shows you the result.
An MCP key, sent as "Authorization: Bearer hk_mcp_…". MCP keys are separate from API keys: make one on the MCP keys page (https://app.housekraft.co/developers/mcp); an API key does not work here, and an MCP key does not work on the API. The same paid-plan rule, credits and refunds apply, and results go to the same Library. Revoking the key stops the assistant at once.
Add it to your assistant
Run this once in a terminal:
claude mcp add --transport http housekraft https://app.housekraft.co/api/v1/mcp \
--header "Authorization: Bearer $HOUSEKRAFT_MCP_KEY"Add this to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):
{
"mcpServers": {
"housekraft": {
"url": "https://app.housekraft.co/api/v1/mcp",
"headers": {
"Authorization": "Bearer hk_mcp_…"
}
}
}
}Add this to .vscode/mcp.json. VS Code asks for the key the first time:
{
"inputs": [
{
"type": "promptString",
"id": "housekraft-mcp-key",
"description": "HouseKraft MCP key",
"password": true
}
],
"servers": {
"housekraft": {
"type": "http",
"url": "https://app.housekraft.co/api/v1/mcp",
"headers": {
"Authorization": "Bearer ${input:housekraft-mcp-key}"
}
}
}
}Settings → Developer → Edit Config, then add this to claude_desktop_config.json and restart. Needs Node.js:
{
"mcpServers": {
"housekraft": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://app.housekraft.co/api/v1/mcp",
"--header",
"Authorization:${HOUSEKRAFT_AUTH}"
],
"env": {
"HOUSEKRAFT_AUTH": "Bearer hk_mcp_…"
}
}
}
}Any MCP client that speaks Streamable HTTP: this address, with this header:
https://app.housekraft.co/api/v1/mcp
Authorization: Bearer hk_mcp_…How it works
- Photos are given as an asset id or an https:// link. The assistant can upload a link or base64 data with upload_image, use a photo you uploaded on the website (list_library), or pass on an earlier result.
- Photos on your computer: just name the file, or a .zip of photos for bulk staging ("stage ~/Desktop/room.jpg in Japandi style"). An assistant that can run commands, such as Claude Code, uploads them itself with a one-time link and carries on. Otherwise, or for a photo you pasted into the chat, the assistant gives you a link: drop the photos there and tell it you are done. Links work for 15 minutes, upload only, and stop if the key is revoked.
- Quick tools wait and return the finished image, with a small preview the assistant can see, and a link to the file that opens without signing in for 60 minutes. Longer ones (videos, bulk jobs, En Masse) return a job, and the assistant checks it with get_job. If a request is missing something (a style, a colour, a video length), the assistant is told what to ask you first; nothing runs until then.
- Name catalogue items in plain words: "paint the walls Sage Whisper", "stage it in a Japandi theme", "try 10 green paints from Asian Paints on this room". The assistant finds them in HouseKraft's catalogues and asks if a name matches several.
- claude.ai and ChatGPT connectors sign in with OAuth, which is not offered yet.
Its tools
| Tool | What it does |
|---|---|
walls | Repaints the walls in the photo with the colour you choose. |
flooring | Replaces the floor with the flooring you choose: wood, tile, marble, vinyl, or a photo of a real product. |
material | Changes the material of a surface, such as a countertop, a wall, cabinets or a sofa, or of several marked areas at once, each with its own material. |
furniture | Adds a piece of furniture, swaps one piece for another, or removes a piece. |
lighting | Relights the room as it would look at another time of day, with the light coming through its windows. |
weather | Changes the weather and the view seen through the windows: rain, snow, fog, a beach or a city outside. |
stage | Furnishes an empty room: in a style you describe, in the style of a reference photo, or with photos of the exact pieces to use. |
unstage | Removes furniture, rugs and decor, leaving the empty room. |
pov | Shows the same room from another camera angle: turn left or right, tilt up or down, zoom in or out. |
editor | Changes only what you point at. |
dollhouse | Turns a 2D floor plan into a top-down 3D "dollhouse" render of the home, furnished in the style you choose. |
panorama | Builds a 360° panorama of a room from 1 to 6 photos taken from about the same spot. |
walkthrough | Turns one room photo into a short video, as if walking through the room. |
walkthrough_multi | Makes one continuous video that moves through several photos in order: different rooms of a home, or several views of one room. |
bulk_stage | Stages a whole listing at once: up to 50 empty rooms in one job, every room in the same style. |
bulk_unstage | Empties up to 50 furnished rooms in one job: furniture, rugs and decor go, walls, floors, windows and built-ins stay. |
enmasse | Shows one room with many options at once. |
upload_image | Add a photo to the user's HouseKraft Library and get its asset id, to use as any tool's image. |
upload_files | For photos on the user's computer, or a .zip of photos (bulk staging): returns a one-time curl command that uploads them to the user's HouseKraft Library and prints their asset ids (run it on the user's computer), and a link the user can open to drop photos in a browser instead. |
get_uploads | The photos the user uploaded through an upload link from upload_files, with their asset ids. |
get_job | A run's status and result. |
list_jobs | The user's recent API and MCP runs, newest first. |
list_library | The user's HouseKraft Library, newest first: photos they uploaded (on the website or here) and results. |
get_account | The user's credit balance and plan. |
browse_catalog | Search HouseKraft's catalogues: themes (whole interior styles), materials (stones, woods, metals, fabrics…), paints (paint brand colours, with colour codes), flooring (tiles and floors from flooring brands) and furniture (from furniture brands). |
get_tool_options | The website's built-in choices for a tool: wall colours, floorings, furniture, staging modes and styles, camera angle presets. |
Try it from a terminal
curl -s -X POST https://app.housekraft.co/api/v1/mcp \
-H "Authorization: Bearer $HOUSEKRAFT_MCP_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'Streamable HTTP (JSON-RPC over POST, plain JSON answers, no sessions). Protocol versions 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05.
Tools
Wall Paint
wallsRepaints the walls in the photo with the colour you choose. Everything else in the room stays as it is.
Photo: A room photo where the walls are clearly visible.
Example: Paint the walls sage green
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "sage green"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Wall Paint
job = call("POST", "/api/v1/tools/walls/run", json={
"image": photo,
"color_id": "sage green",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Wall Paint
let { job } = await call("POST", "/api/v1/tools/walls/run", {
"image": photo,
"color_id": "sage green",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/walls/options - Run Wall Paint
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "color_id": "sage green" }' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
color_id |
text | A colour from the wall colour list (GET /api/v1/tools/walls/options), a hex code such as "#8A9A5B", or a colour or finish name such as "sage green". |
paint |
catalog | Or a brand paint colour from HouseKraft's catalogue, by name or id, e.g. "Sage Whisper" (GET /api/v1/catalog/paints). |
paint_sample |
image | Optional. A photo of the exact paint to match. |
mask |
image | Optional. A black-and-white mask: white where the walls should change. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Give at least one of: color_id, paint.
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/walls/options.
More examples
A brand paint colour from the catalogue
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"paint": "Sage Whisper"
}'Use an exact hex colour
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id from POST /api/v1/uploads>", "color_id": "#8A9A5B"}'Match a photo of a paint swatch
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "Antique White",
"paint_sample": "<asset id of a paint swatch>"
}'Paint only some walls, using a mask
curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "navy blue",
"mask": "<asset id of a black-and-white mask>"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walls",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "sage green"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "walls",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walls",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "sage green"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walls",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"color_id": "sage green"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- color_id accepts an id or name from GET /api/v1/tools/walls/options, a hex code, or any colour or finish name.
- A mask is a black-and-white image the same size as the photo: white where the walls should change.
- The walls must be visible in the photo.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per run, refunded if the run fails.
This tool's help as JSON →Flooring
flooringReplaces the floor with the flooring you choose: wood, tile, marble, vinyl, or a photo of a real product. Walls and furniture stay as they are.
Photo: A room photo where the floor is clearly visible.
Example: Lay oak hardwood
curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"flooring_id": "oak_hardwood"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Flooring
job = call("POST", "/api/v1/tools/flooring/run", json={
"image": photo,
"flooring_id": "oak_hardwood",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Flooring
let { job } = await call("POST", "/api/v1/tools/flooring/run", {
"image": photo,
"flooring_id": "oak_hardwood",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/flooring/options - Run Flooring
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "flooring_id": "oak_hardwood" }' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
flooring_id |
text | A flooring from the list (GET /api/v1/tools/flooring/options), a flooring name, or "custom_upload" together with flooring_sample. |
flooring_product |
catalog | Or a tile or floor from a brand in HouseKraft's catalogue, by name or id, e.g. "Kajaria Beige Marble" (GET /api/v1/catalog/flooring). |
flooring_sample |
image | Optional. A photo of the tile or flooring to lay. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Give at least one of: flooring_id, flooring_product.
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/flooring/options.
More examples
A brand tile from the catalogue
curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"flooring_product": "Kajaria Beige Marble"
}'Lay your own tile from a photo
curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"flooring_id": "custom_upload",
"flooring_sample": "<asset id of a tile photo>"
}'Describe the flooring by name
curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"flooring_id": "herringbone oak"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "flooring",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"flooring_id": "oak_hardwood"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "flooring",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "flooring",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"flooring_id": "oak_hardwood"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "flooring",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"flooring_id": "oak_hardwood"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- See GET /api/v1/tools/flooring/options for the flooring list.
- To use a product photo, send flooring_id "custom_upload" and the photo as flooring_sample.
- The floor must be visible in the photo.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per run, refunded if the run fails.
This tool's help as JSON →Material
materialChanges the material of a surface, such as a countertop, a wall, cabinets or a sofa, or of several marked areas at once, each with its own material.
Photo: A room photo where the surface to change is clearly visible.
Example: White marble on the kitchen countertop
curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"material_id": "white_marble",
"custom_prompt": "the kitchen countertop"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Material
job = call("POST", "/api/v1/tools/material/run", json={
"image": photo,
"material_id": "white_marble",
"custom_prompt": "the kitchen countertop",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Material
let { job } = await call("POST", "/api/v1/tools/material/run", {
"image": photo,
"material_id": "white_marble",
"custom_prompt": "the kitchen countertop",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run Material
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "material_id": "white_marble", "custom_prompt": "the kitchen countertop" }' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
material_id |
text | Optional. A material id or name, e.g. "white_marble". |
library_material |
catalog | Or a material from HouseKraft's material library, by name or id, e.g. "Calacatta Gold Marble" (GET /api/v1/catalog/materials). |
custom_prompt |
text | Optional. Describe the material or where it goes. |
material_image |
image | Optional. A photo of the material. |
placements |
placements | Optional. Several areas, each with its own material: a list of {"name": "oak", "image": "<asset id or URL>", "position": {"x": 30, "y": 70, "width": 25, "height": 20}}. Each needs a name or an image. Positions are percentages of the photo (x, y = centre).Up to 8 |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Give at least one of: material_id, library_material, custom_prompt, placements.
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
A material from the library
curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"library_material": "Calacatta Gold Marble",
"custom_prompt": "the kitchen countertop"
}'Describe the material in words
curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"custom_prompt": "dark green zellige tiles on the backsplash"
}'Use a photo of the material
curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"custom_prompt": "the kitchen island",
"material_image": "<asset id of a material photo>"
}'Several areas at once, each with its own material
curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"placements": [
{"name": "oak", "position": {"x": 30, "y": 75, "width": 30, "height": 20}},
{"name": "brass", "image": "<asset id of a brass swatch>", "position": {"x": 70, "y": 40, "width": 15, "height": 15}}
]
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "material",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"material_id": "white_marble",
"custom_prompt": "the kitchen countertop"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "material",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"material_id": "white_marble",
"custom_prompt": "the kitchen countertop"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "material",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "material",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"material_id": "white_marble",
"custom_prompt": "the kitchen countertop"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- Give material_id, custom_prompt or placements.
- placements changes up to 8 areas at once. Each area is a box in percentages of the photo (x, y = centre of the box).
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 2 credits per run, refunded if the run fails.
This tool's help as JSON →Object Placement
furnitureAdds a piece of furniture, swaps one piece for another, or removes a piece. Say where with a box on the photo, or let the model choose.
Photo: A room photo with space for the piece, or showing the piece to replace or remove.
Example: Add a walnut side table
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_type": "walnut side table",
"position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Object Placement
job = call("POST", "/api/v1/tools/furniture/run", json={
"image": photo,
"action": "add",
"furniture_type": "walnut side table",
"position": {"x": 50, "y": 65, "width": 22, "height": 25},
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Object Placement
let { job } = await call("POST", "/api/v1/tools/furniture/run", {
"image": photo,
"action": "add",
"furniture_type": "walnut side table",
"position": {"x": 50, "y": 65, "width": 22, "height": 25},
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/furniture/options - Run Object Placement
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "action": "add", "furniture_type": "walnut side table", "position": {"x": 50, "y": 65, "width": 22, "height": 25} }' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
action |
choice | What to do.One of: add, replace, remove · Default: add |
furniture_type |
text | The piece, e.g. "walnut side table" (GET /api/v1/tools/furniture/options). |
custom_prompt |
text | Optional. Any extra description. |
furniture_product |
catalog | Or a piece from a furniture brand in HouseKraft's catalogue, by name or id (GET /api/v1/catalog/furniture). |
furniture_image |
image | Optional. A photo of the piece to add or swap in. |
position |
box | Optional. Where it goes. A box on the photo as percentages: {"x": 50, "y": 60, "width": 20, "height": 25} (x, y = centre). |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/furniture/options.
More examples
A brand piece from the catalogue
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_product": "Ekenas Armchair"
}'Replace a sofa
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"action": "replace",
"furniture_type": "green velvet sofa",
"position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'Remove a piece
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"action": "remove",
"position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'Add a piece from a product photo
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_image": "<asset id of the armchair>",
"position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'Let the model choose where it goes
curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_type": "arched floor lamp"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "furniture",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_type": "walnut side table",
"position": {
"x": 50,
"y": 65,
"width": 22,
"height": 25
}
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "furniture",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_type": "walnut side table",
"position": {
"x": 50,
"y": 65,
"width": 22,
"height": 25
}
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "furniture",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "furniture",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"action": "add",
"furniture_type": "walnut side table",
"position": {
"x": 50,
"y": 65,
"width": 22,
"height": 25
}
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- position is a box on the photo in percentages: x and y are its centre, width and height its size. {"x": 50, "y": 65, "width": 22, "height": 25} is a box in the lower middle.
- For remove, give the box around the piece to remove.
- See GET /api/v1/tools/furniture/options for furniture names.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 2 credits per run, refunded if the run fails.
This tool's help as JSON →Time of Day
lightingRelights the room as it would look at another time of day, with the light coming through its windows.
Photo: A room photo with a window, glass door or skylight.
Example: Change the light to sunset
curl -s -X POST https://app.housekraft.co/api/v1/tools/lighting/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"time_of_day": "sunset"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Time of Day
job = call("POST", "/api/v1/tools/lighting/run", json={
"image": photo,
"time_of_day": "sunset",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Time of Day
let { job } = await call("POST", "/api/v1/tools/lighting/run", {
"image": photo,
"time_of_day": "sunset",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run Time of Day
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/lighting/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "time_of_day": "sunset" }' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
time_of_dayrequired |
choice | The time of day.One of: sunrise, before_noon, noon, afternoon, early_evening, sunset, night |
custom_prompt |
text | Optional. Anything else about the light. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Night, with the lamps on
curl -s -X POST https://app.housekraft.co/api/v1/tools/lighting/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"time_of_day": "night",
"custom_prompt": "warm lamps on"
}'Early morning light
curl -s -X POST https://app.housekraft.co/api/v1/tools/lighting/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"time_of_day": "sunrise"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "lighting",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"time_of_day": "sunset"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "lighting",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "lighting",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"time_of_day": "sunset"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "lighting",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"time_of_day": "sunset"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- time_of_day is one of: sunrise, before_noon, noon, afternoon, early_evening, sunset, night.
- The photo needs a window, glass door or skylight: the light changes through it.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per run, refunded if the run fails.
This tool's help as JSON →Weather
weatherChanges the weather and the view seen through the windows: rain, snow, fog, a beach or a city outside.
Photo: A room photo with a window or glass door.
Example: Make it snow outside
curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id from POST /api/v1/uploads>", "weather": "snowy"}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Weather
job = call("POST", "/api/v1/tools/weather/run", json={
"image": photo,
"weather": "snowy",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Weather
let { job } = await call("POST", "/api/v1/tools/weather/run", {
"image": photo,
"weather": "snowy",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run Weather
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{"image": "<asset id from POST /api/v1/uploads>", "weather": "snowy"}' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
weather |
choice | The weather.One of: sunny, cloudy, overcast, rainy, thunderstorm, stormy, foggy, snowy, winter, summer, autumn, spring |
location |
text | Optional. Where the view is, e.g. "Lisbon" or "beach". |
time_of_day |
choice | Optional. The time of day.One of: sunrise, before_noon, noon, afternoon, early_evening, sunset, night |
custom_prompt |
text | Optional. Describe the weather in your own words. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Give at least one of: weather, location, custom_prompt, time_of_day.
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Rain in Lisbon
curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"weather": "rainy",
"location": "Lisbon"
}'A beach at sunset
curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"location": "beach",
"time_of_day": "sunset"
}'Describe it in your own words
curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"custom_prompt": "light fog over a pine forest"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "weather",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"weather": "snowy"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "lighting",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "weather",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"weather": "snowy"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "weather",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"weather": "snowy"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- Give at least one of weather, location, time_of_day or custom_prompt.
- Changes what is seen through existing windows; it never adds windows.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per run, refunded if the run fails.
This tool's help as JSON →Stage
stageFurnishes an empty room: in a style you describe, in the style of a reference photo, or with photos of the exact pieces to use.
Photo: An empty room. For a furnished room, run unstage first and stage the result.
Example: Stage in a Scandinavian style
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "Scandinavian, light oak and linen"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Stage
job = call("POST", "/api/v1/tools/stage/run", json={
"image": photo,
"prompt": "Scandinavian, light oak and linen",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Stage
let { job } = await call("POST", "/api/v1/tools/stage/run", {
"image": photo,
"prompt": "Scandinavian, light oak and linen",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/stage/options - Run Stage
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "prompt": "Scandinavian, light oak and linen" }' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The empty room to furnish. |
prompt |
text | Optional. Style or anything specific, e.g. "Scandinavian, light oak". |
staging_mode |
choice | How to stage: from the prompt, from a reference photo, or with specific furniture photos.One of: prompt_only, reference, furniture · Default: prompt_only |
reference_image |
image | For staging_mode "reference": a style reference or moodboard. |
furniture_images |
images | For staging_mode "furniture": photos of the pieces to use.1–6 images |
theme_name |
text | Optional. The name of a theme, if reference_image is one. |
theme |
catalog | Or a theme from HouseKraft's catalogue, by name or id, e.g. "Japandi Calm" (GET /api/v1/catalog/themes): the room is staged in it. |
theme_category |
catalog_group | Or a theme category (GET /api/v1/catalog/themes/groups), e.g. "Japandi": one theme from it is used, the first, or a random one with random_theme. |
random_theme |
boolean | With theme_category: pick the theme at random instead of the first. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/stage/options.
More examples
Stage in a catalogue theme
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"theme": "Japandi Calm"
}'Stage in a random theme from a category
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"theme_category": "Japandi",
"random_theme": true
}'Copy the style of a moodboard
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"staging_mode": "reference",
"reference_image": "<asset id of a moodboard>"
}'Use these exact pieces
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"staging_mode": "furniture",
"furniture_images": ["<asset id of a sofa>", "<asset id of a lamp>"]
}'Use a named theme photo
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"staging_mode": "reference",
"reference_image": "<asset id of a theme photo>",
"theme_name": "Japandi"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "stage",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "Scandinavian, light oak and linen"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "stage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "stage",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "Scandinavian, light oak and linen"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "stage",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "Scandinavian, light oak and linen"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- Made for empty rooms. For a furnished room, run unstage first and stage the result.
- staging_mode "furniture" takes up to 6 furniture photos.
- See GET /api/v1/tools/stage/options for the website's styles.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 2 credits per run, refunded if the run fails.
This tool's help as JSON →Unstage
unstageRemoves furniture, rugs and decor, leaving the empty room. Walls, floor, windows and built-ins stay.
Photo: A furnished room.
Example: Empty the room
curl -s -X POST https://app.housekraft.co/api/v1/tools/unstage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id from POST /api/v1/uploads>"}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Unstage
job = call("POST", "/api/v1/tools/unstage/run", json={
"image": photo,
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Unstage
let { job } = await call("POST", "/api/v1/tools/unstage/run", {
"image": photo,
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run Unstage
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/unstage/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{"image": "<asset id from POST /api/v1/uploads>"}' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The furnished room to empty. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Use a photo straight from a URL, without uploading
curl -s -X POST https://app.housekraft.co/api/v1/tools/unstage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "https://example.com/listing/living-room.jpg"}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "unstage",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "unstage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "unstage",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "unstage",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- Removes furniture, rugs and decor. Walls, floor, windows and built-ins stay.
- To restyle a furnished room: unstage it, then stage the result (send its output.id as image).
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per run, refunded if the run fails.
This tool's help as JSON →POV
povShows the same room from another camera angle: turn left or right, tilt up or down, zoom in or out.
Photo: A room photo.
Example: Turn the camera 45° to the left
curl -s -X POST https://app.housekraft.co/api/v1/tools/pov/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"azimuth": -45,
"elevation": 0,
"zoom": 5
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run POV
job = call("POST", "/api/v1/tools/pov/run", json={
"image": photo,
"azimuth": -45,
"elevation": 0,
"zoom": 5,
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run POV
let { job } = await call("POST", "/api/v1/tools/pov/run", {
"image": photo,
"azimuth": -45,
"elevation": 0,
"zoom": 5,
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/pov/options - Run POV
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/pov/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "azimuth": -45, "elevation": 0, "zoom": 5 }' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
azimuth |
number | Move the camera around the room, in degrees: negative to the left, positive to the right (90 = the right side, 180 = from behind).-180 to 180 · Default: 0 |
elevation |
number | Camera height, in degrees: positive raises the camera to look down on the room (60 = bird's eye, 90 = straight down); negative lowers it to look up.-30 to 90 · Default: 0 |
zoom |
number | Zoom, 0 (wide) to 10 (close).0 to 10 · Default: 5 |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/pov/options.
More examples
Look down from above
curl -s -X POST https://app.housekraft.co/api/v1/tools/pov/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"azimuth": 0,
"elevation": 45,
"zoom": 3
}'Move in close
curl -s -X POST https://app.housekraft.co/api/v1/tools/pov/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"azimuth": 0,
"elevation": 0,
"zoom": 8
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "pov",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"azimuth": -45,
"elevation": 0,
"zoom": 5
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "pov",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"azimuth": -45,
"elevation": 0,
"zoom": 5
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "pov",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "pov",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"azimuth": -45,
"elevation": 0,
"zoom": 5
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- azimuth turns the camera (-180 to 180), elevation tilts it (-30 to 90), zoom 0 is wide and 10 is close.
- See GET /api/v1/tools/pov/options for the website's named angles.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 2 credits per run, refunded if the run fails.
This tool's help as JSON →AI Annotate
editorChanges only what you point at. Mark the object with a brush stroke, an arrow or a text note and say what to change; the rest of the photo stays the same.
Photo: A room photo.
Example: Describe the change in words
curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "make the pendant lamp brass"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run AI Annotate
job = call("POST", "/api/v1/tools/editor/run", json={
"image": photo,
"prompt": "make the pendant lamp brass",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run AI Annotate
let { job } = await call("POST", "/api/v1/tools/editor/run", {
"image": photo,
"prompt": "make the pendant lamp brass",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run AI Annotate
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "prompt": "make the pendant lamp brass" }' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
prompt |
text | What to change, e.g. "make this lamp brass". |
annotations |
annotations | Optional. Marks on the photo, as the website canvas draws them: {"type": "brush", "points": [{"x":..,"y":..}], "color": "#ffff00", "width": 20}, {"type": "arrow", "start": {"x":..,"y":..}, "end": {"x":..,"y":..}}, {"type": "text", "text": "...", "x": .., "y": ..}. Coordinates are pixels of the photo, or of a ref_w × ref_h preview.Up to 50 |
ref_w |
integer | Optional. Width of the preview the marks were drawn on.0 to 20000 |
ref_h |
integer | Optional. Height of that preview.0 to 20000 |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Give at least one of: prompt, annotations.
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Brush over the object, then say what to do
curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "make this brass",
"annotations": [
{"type": "brush", "points": [{"x": 410, "y": 120}, {"x": 450, "y": 160}], "width": 30}
]
}'An arrow and a note, no prompt
curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"annotations": [
{"type": "arrow", "start": {"x": 200, "y": 300}, "end": {"x": 320, "y": 260}},
{"type": "text", "text": "remove this chair", "x": 180, "y": 320}
]
}'Marks drawn on a smaller preview of the photo
curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "replace with a round mirror",
"ref_w": 800,
"ref_h": 600,
"annotations": [
{"type": "brush", "points": [{"x": 300, "y": 150}, {"x": 340, "y": 190}], "width": 25}
]
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "editor",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 3,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "make the pendant lamp brass"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "editor",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 3,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "make the pendant lamp brass"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "editor",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "editor",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 3,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"prompt": "make the pendant lamp brass"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- Coordinates are pixels of the photo. If you drew on a smaller preview, send its size as ref_w and ref_h and the marks are scaled.
- The marks are drawn onto a copy that the model sees next to the clean photo, and only the marked object changes.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 3 credits per run, refunded if the run fails.
This tool's help as JSON →Floor Plan to Dollhouse
dollhouseTurns a 2D floor plan into a top-down 3D "dollhouse" render of the home, furnished in the style you choose.
Photo: A floor plan, blueprint or sketch, not a photo of a room.
Example: Render a floor plan in a Scandinavian style
curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id of a floor plan>", "style": "scandinavian"}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("floor-plan.png")
# 2. Run Floor Plan to Dollhouse
job = call("POST", "/api/v1/tools/dollhouse/run", json={
"image": photo,
"style": "scandinavian",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("floor-plan.png");
// 2. Run Floor Plan to Dollhouse
let { job } = await call("POST", "/api/v1/tools/dollhouse/run", {
"image": photo,
"style": "scandinavian",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@floor-plan.png - Run Floor Plan to Dollhouse
Waits for the result (up to 50 s) and answers with the finished job.
curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{"image": "<asset id of a floor plan>", "style": "scandinavian"}' - Get the result
If the answer says "done", the file is at job.output.url. If it says "running", fetch the job until it is done.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The floor plan. |
style |
choice | Interior style.One of: modern, scandinavian, luxury, mediterranean, industrial, japandi · Default: modern |
custom_style |
text | Optional. Describe the style in your own words. |
reference_image |
image | Optional. A style reference or theme photo. |
theme_name |
text | Optional. The theme's name, if reference_image is one. |
theme |
catalog | Or a theme from HouseKraft's catalogue, by name or id (GET /api/v1/catalog/themes). |
theme_category |
catalog_group | Or a theme category (GET /api/v1/catalog/themes/groups), e.g. "Japandi": one theme from it is used, the first, or a random one with random_theme. |
random_theme |
boolean | With theme_category: pick the theme at random instead of the first. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Furnish it in a catalogue theme
curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"theme": "Japandi Calm"
}'Describe the style in your own words
curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id of a floor plan>",
"custom_style": "warm terracotta and rattan"
}'Use a theme photo
curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id of a floor plan>",
"reference_image": "<asset id of a theme photo>",
"theme_name": "Japandi"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "dollhouse",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id of a floor plan>",
"style": "scandinavian"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "dollhouse",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "dollhouse",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 1,
"refunded": false
},
"input": {
"image": "<asset id of a floor plan>",
"style": "scandinavian"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Still running after 50 seconds. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "dollhouse",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 1,
"refunded": true
},
"input": {
"image": "<asset id of a floor plan>",
"style": "scandinavian"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- style is one of: modern, scandinavian, luxury, mediterranean, industrial, japandi.
- The image must be a floor plan, blueprint or sketch, not a photo of a room.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per run, refunded if the run fails.
This tool's help as JSON →360° Panorama
panoramaBuilds a 360° panorama of a room from 1 to 6 photos taken from about the same spot. The result is an equirectangular image (2:1) that any 360° viewer can show.
Photo: 1 to 6 photos of the same room, taken from roughly one spot.
Example: A 360° panorama from three photos
curl -s -X POST https://app.housekraft.co/api/v1/tools/panorama/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<photo 1>", "<photo 2>", "<photo 3>"]}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photos
photos = [upload(path) for path in ["view-1.jpg", "view-2.jpg", "view-3.jpg"]]
# 2. Run 360° Panorama
job = call("POST", "/api/v1/tools/panorama/run", json={
"images": photos,
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photos
const photos = await Promise.all(["view-1.jpg", "view-2.jpg", "view-3.jpg"].map(upload));
// 2. Run 360° Panorama
let { job } = await call("POST", "/api/v1/tools/panorama/run", {
"images": photos,
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photos, one request each
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@view-1.jpg - Run 360° Panorama
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/panorama/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{"images": ["<photo 1>", "<photo 2>", "<photo 3>"]}' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagesrequired |
images | 1–6 photos of the same room, taken from roughly one spot. Order does not matter.1–6 images |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "panorama",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 3,
"refunded": false
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "panorama",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 3,
"refunded": false
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
]
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "panorama",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 2048,
"height": 1024,
"created_at": 1793700025,
"kind": "panorama"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "panorama",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 3,
"refunded": true
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- Stand in one spot and shoot in different directions; more photos means less guessing.
- The price is the same whether you send one photo or six.
- The result is a flat equirectangular image: open it in a 360° viewer (Pannellum, Photo Sphere Viewer, three.js) to look around.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 3 credits per run, refunded if the run fails.
This tool's help as JSON →Walkthrough Video
walkthroughTurns one room photo into a short video, as if walking through the room.
Photo: A wide room photo with space to move through.
Example: A 6-second walkthrough
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id from POST /api/v1/uploads>", "duration": 6}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photo
photo = upload("room.jpg")
# 2. Run Walkthrough Video
job = call("POST", "/api/v1/tools/walkthrough/run", json={
"image": photo,
"duration": 6,
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photo
const photo = await upload("room.jpg");
// 2. Run Walkthrough Video
let { job } = await call("POST", "/api/v1/tools/walkthrough/run", {
"image": photo,
"duration": 6,
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photo
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run Walkthrough Video
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{"image": "<asset id from POST /api/v1/uploads>", "duration": 6}' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
duration |
integer | Video length in seconds.1 to 15 · Default: 6 |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
A longer, 10-second video
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"image": "<asset id from POST /api/v1/uploads>", "duration": 10}'A short video, waiting for it in the same request
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"duration": 4,
"wait": true
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walkthrough",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 10,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"duration": 6
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walkthrough",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 10,
"refunded": false
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"duration": 6
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "walkthrough",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
"thumbnail_url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
"width": null,
"height": null,
"created_at": 1793700025,
"kind": "video"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walkthrough",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 10,
"refunded": true
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"duration": 6
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- The price depends on the length: see GET /api/v1/tools/pricing.
- A wide shot with room to move through works best.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. Priced by video length; see GET /api/v1/tools/pricing. Refunded if the run fails.
This tool's help as JSON →Walkthrough Video (multiple photos)
walkthrough_multiMakes one continuous video that moves through several photos in order: different rooms of a home, or several views of one room.
Photo: 1 to 7 photos, in the order the camera should visit them.
Example: A walkthrough of a home from three photos
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": ["<front of house>", "<hallway>", "<living room>"],
"duration": 8,
"scope": "property"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photos
photos = [upload(path) for path in ["front-of-house.jpg", "hallway.jpg", "living-room.jpg"]]
# 2. Run Walkthrough Video (multiple photos)
job = call("POST", "/api/v1/tools/walkthrough_multi/run", json={
"images": photos,
"duration": 8,
"scope": "property",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
print("Result:", job["output"]["url"])
else:
print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photos
const photos = await Promise.all(["front-of-house.jpg", "hallway.jpg", "living-room.jpg"].map(upload));
// 2. Run Walkthrough Video (multiple photos)
let { job } = await call("POST", "/api/v1/tools/walkthrough_multi/run", {
"images": photos,
"duration": 8,
"scope": "property",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photos, one request each
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@front-of-house.jpg - Run Walkthrough Video (multiple photos)
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "images": ["<front of house>", "<hallway>", "<living room>"], "duration": 8, "scope": "property" }' - Get the result
Fetch the job every few seconds until status is "done"; the file is at job.output.url.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagesrequired |
images | 1–7 photos, in the order the camera should visit them.1–7 images |
scope |
choice | "property" for different rooms of one home, "room" for several views of one room.One of: property, room · Default: property |
duration |
integer | Video length in seconds.4 to 15 · Default: 6 |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Several views of one room
curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<view 1>", "<view 2>"], "scope": "room", "duration": 6}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walkthrough_multi",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"images": [
"<front of house>",
"<hallway>",
"<living room>"
],
"duration": 8,
"scope": "property"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walkthrough_multi",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": false
},
"input": {
"images": [
"<front of house>",
"<hallway>",
"<living room>"
],
"duration": 8,
"scope": "property"
},
"warnings": [],
"check_overridden": false,
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "walkthrough",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
"thumbnail_url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
"width": null,
"height": null,
"created_at": 1793700025,
"kind": "video"
},
"error": null
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "walkthrough_multi",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 2,
"refunded": true
},
"input": {
"images": [
"<front of house>",
"<hallway>",
"<living room>"
],
"duration": 8,
"scope": "property"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "The image model could not finish this request."
}
}
}Ids, links and times above are examples.
Tips
- List the photos in the order the camera should visit them.
- The price depends on the length and the number of photos: see GET /api/v1/tools/pricing.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. Priced by video length and number of photos; see GET /api/v1/tools/pricing. Refunded if the run fails.
This tool's help as JSON →Bulk Staging
bulk_stageStages a whole listing at once: up to 50 empty rooms in one job, every room in the same style. The photos are worked on a few at a time, and each result is ready as soon as its photo is done.
Photo: Photos of empty rooms. For furnished rooms, run bulk_unstage first.
Example: Stage three empty rooms in one style
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": ["<photo 1>", "<photo 2>", "<photo 3>"],
"prompt": "Scandinavian, light oak and linen"
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photos
photos = [upload(path) for path in ["room-1.jpg", "room-2.jpg", "room-3.jpg"]]
# 2. Run Bulk Staging
job = call("POST", "/api/v1/tools/bulk_stage/run", json={
"images": photos,
"prompt": "Scandinavian, light oak and linen",
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
for item in job["outputs"]: # one per photo or reference, in order
print(item["index"], item["status"], item["output"]["url"] if item["output"] else item["error"])
else:
print("Failed:", job["error"]["message"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photos
const photos = await Promise.all(["room-1.jpg", "room-2.jpg", "room-3.jpg"].map(upload));
// 2. Run Bulk Staging
let { job } = await call("POST", "/api/v1/tools/bulk_stage/run", {
"images": photos,
"prompt": "Scandinavian, light oak and linen",
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") {
// one per photo or reference, in order
for (const item of job.outputs) console.log(item.index, item.status, item.output ? item.output.url : item.error);
} else {
console.log("Failed:", job.error.message);
}
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photos, one request each
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room-1.jpg - Run Bulk Staging
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "images": ["<photo 1>", "<photo 2>", "<photo 3>"], "prompt": "Scandinavian, light oak and linen" }' - Get the results
Fetch the job every few seconds until status is "done". Each result is in job.outputs, in order, with its own status; its file is at output.url. job.progress counts them.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagesrequired |
images | 1–50 photos of empty rooms. Each gets its own result.1–50 images |
prompt |
text | Optional. The style for every room, e.g. "Scandinavian, light oak". |
reference_image |
image | Optional. A theme or style photo: every room is staged in its style. |
theme |
catalog | Or a theme from HouseKraft's catalogue, by name or id (GET /api/v1/catalog/themes): every room is staged in it. |
theme_category |
catalog_group | Or a theme category (GET /api/v1/catalog/themes/groups), e.g. "Japandi": one theme from it is used, the first, or a random one with random_theme. |
random_theme |
boolean | With theme_category: pick the theme at random instead of the first. |
sku |
text | Optional. A listing id saved on every result, to find them later (letters, numbers, - _ .). One is made up if you leave it out. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Every room in a catalogue theme
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": ["<photo 1>", "<photo 2>", "<photo 3>"],
"theme": "Japandi Calm"
}'Every room in a theme from a category
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<photo 1>", "<photo 2>"], "theme_category": "Scandinavian"}'Stage every room in the style of a theme photo
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": ["<photo 1>", "<photo 2>"],
"reference_image": "<asset id of a theme photo>"
}'Tag the results with a listing id
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": ["<photo 1>", "<photo 2>"],
"prompt": "modern farmhouse",
"sku": "MLS-2291"
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "bulk_stage",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 4,
"refunded": false,
"refunded_credits": 0
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
],
"prompt": "Scandinavian, light oak and linen"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"progress": {
"total": 3,
"done": 1,
"failed": 0
},
"outputs": [
{
"index": 0,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "stage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 1,
"status": "running",
"output": null,
"error": null
},
{
"index": 2,
"status": "waiting",
"output": null,
"error": null
}
],
"sku": "HK-3F9A1C2B",
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "bulk_stage",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 6,
"refunded": false,
"refunded_credits": 0
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
],
"prompt": "Scandinavian, light oak and linen"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"progress": {
"total": 3,
"done": 3,
"failed": 0
},
"outputs": [
{
"index": 0,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "stage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 1,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6",
"type": "generated",
"tool": "stage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 2,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7",
"type": "generated",
"tool": "stage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
],
"sku": "HK-3F9A1C2B"
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "bulk_stage",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 0,
"refunded": true,
"refunded_credits": 6
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
],
"prompt": "Scandinavian, light oak and linen"
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "None of the photos could be finished. Their credits have been refunded."
},
"progress": {
"total": 3,
"done": 0,
"failed": 3
},
"outputs": [
{
"index": 0,
"status": "failed",
"output": null,
"error": "The image model could not finish this photo."
},
{
"index": 1,
"status": "failed",
"output": null,
"error": "The image model could not finish this photo."
},
{
"index": 2,
"status": "failed",
"output": null,
"error": "The image model could not finish this photo."
}
],
"sku": "HK-3F9A1C2B"
}
}Ids, links and times above are examples.
Tips
- Have the photos in a .zip? Upload it to POST /api/v1/uploads and send the asset ids it answers with (assets[].id).
- Each photo is charged when it starts and refunded if it fails: job.credits shows the total.
- job.outputs lists every photo in the order you sent them, each with its own status and result.
- One bulk job at a time per account, on the website and the API together.
- The batch also shows in Bulk on the website.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 2 credits per photo, each refunded if that photo fails.
This tool's help as JSON →Bulk Unstaging
bulk_unstageEmpties up to 50 furnished rooms in one job: furniture, rugs and decor go, walls, floors, windows and built-ins stay. Each result is ready as soon as its photo is done.
Photo: Photos of furnished rooms.
Example: Empty three rooms
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_unstage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<photo 1>", "<photo 2>", "<photo 3>"]}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photos
photos = [upload(path) for path in ["room-1.jpg", "room-2.jpg", "room-3.jpg"]]
# 2. Run Bulk Unstaging
job = call("POST", "/api/v1/tools/bulk_unstage/run", json={
"images": photos,
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
for item in job["outputs"]: # one per photo or reference, in order
print(item["index"], item["status"], item["output"]["url"] if item["output"] else item["error"])
else:
print("Failed:", job["error"]["message"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photos
const photos = await Promise.all(["room-1.jpg", "room-2.jpg", "room-3.jpg"].map(upload));
// 2. Run Bulk Unstaging
let { job } = await call("POST", "/api/v1/tools/bulk_unstage/run", {
"images": photos,
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") {
// one per photo or reference, in order
for (const item of job.outputs) console.log(item.index, item.status, item.output ? item.output.url : item.error);
} else {
console.log("Failed:", job.error.message);
}
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photos, one request each
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room-1.jpg - Run Bulk Unstaging
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_unstage/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{"images": ["<photo 1>", "<photo 2>", "<photo 3>"]}' - Get the results
Fetch the job every few seconds until status is "done". Each result is in job.outputs, in order, with its own status; its file is at output.url. job.progress counts them.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagesrequired |
images | 1–50 photos of furnished rooms. Each gets its own result.1–50 images |
sku |
text | Optional. A listing id saved on every result, to find them later (letters, numbers, - _ .). One is made up if you leave it out. |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | true to run even if the image check rejects the photo. Charged as usual. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Tag the results with a listing id
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_unstage/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<photo 1>", "<photo 2>"], "sku": "MLS-2291"}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "bulk_unstage",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 2,
"refunded": false,
"refunded_credits": 0
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"progress": {
"total": 3,
"done": 1,
"failed": 0
},
"outputs": [
{
"index": 0,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "unstage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 1,
"status": "running",
"output": null,
"error": null
},
{
"index": 2,
"status": "waiting",
"output": null,
"error": null
}
],
"sku": "HK-3F9A1C2B",
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "bulk_unstage",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 3,
"refunded": false,
"refunded_credits": 0
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"progress": {
"total": 3,
"done": 3,
"failed": 0
},
"outputs": [
{
"index": 0,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "unstage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 1,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6",
"type": "generated",
"tool": "unstage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 2,
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7",
"type": "generated",
"tool": "unstage",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
],
"sku": "HK-3F9A1C2B"
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "bulk_unstage",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 0,
"refunded": true,
"refunded_credits": 3
},
"input": {
"images": [
"<photo 1>",
"<photo 2>",
"<photo 3>"
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "None of the photos could be finished. Their credits have been refunded."
},
"progress": {
"total": 3,
"done": 0,
"failed": 3
},
"outputs": [
{
"index": 0,
"status": "failed",
"output": null,
"error": "The image model could not finish this photo."
},
{
"index": 1,
"status": "failed",
"output": null,
"error": "The image model could not finish this photo."
},
{
"index": 2,
"status": "failed",
"output": null,
"error": "The image model could not finish this photo."
}
],
"sku": "HK-3F9A1C2B"
}
}Ids, links and times above are examples.
Tips
- Have the photos in a .zip? Upload it to POST /api/v1/uploads and send the asset ids it answers with (assets[].id).
- To restage a furnished listing: bulk_unstage, then bulk_stage the results (their output.id).
- Each photo is charged when it starts and refunded if it fails: job.credits shows the total.
- One bulk job at a time per account, on the website and the API together.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per photo, each refunded if that photo fails.
This tool's help as JSON →En Masse
enmasseShows one room with many options at once. Send a room photo and 2 to 52 wall colours, floors or themes, and get one result for each. Each result changes only its own part of the room.
Photo: A room photo, plus pictures of the floors or themes to try. Wall colours can be hex codes.
Example: Two wall colours and a floor
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{"mode": "walls", "color": "#8A9A5B"},
{"mode": "walls", "color": "#F2EFE6"},
{"mode": "flooring", "image": "<asset id of a floor photo>"}
]
}'# pip install requests
import os
import time
import requests
API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}
def call(method, path, **kwargs):
response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
data = response.json()
if response.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
def upload(path):
with open(path, "rb") as f:
return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]
# 1. Upload the photos
photo = upload("room.jpg")
floor = upload("oak-floor.jpg")
# 2. Run En Masse
job = call("POST", "/api/v1/tools/enmasse/run", json={
"image": photo,
"references": [
{"mode": "walls", "color": "#8A9A5B"},
{"mode": "walls", "color": "#F2EFE6"},
{"mode": "flooring", "image": floor}
],
})["job"]
# 3. If it is still running, check again until it finishes
while job["status"] == "running":
time.sleep(job.get("poll_after_seconds", 3))
job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]
if job["status"] == "done":
for item in job["outputs"]: # one per photo or reference, in order
print(item["index"], item["status"], item["output"]["url"] if item["output"] else item["error"])
else:
print("Failed:", job["error"]["message"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";
const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };
async function call(method, path, body) {
const options = { method, headers: { ...HEADERS } };
if (body instanceof FormData) {
options.body = body;
} else if (body) {
options.headers["Content-Type"] = "application/json";
options.body = JSON.stringify(body);
}
const response = await fetch(API + path, options);
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
async function upload(path) {
const form = new FormData();
form.append("image", new Blob([await readFile(path)]), path);
return (await call("POST", "/api/v1/uploads", form)).asset.id;
}
// 1. Upload the photos
const photo = await upload("room.jpg");
const floor = await upload("oak-floor.jpg");
// 2. Run En Masse
let { job } = await call("POST", "/api/v1/tools/enmasse/run", {
"image": photo,
"references": [
{"mode": "walls", "color": "#8A9A5B"},
{"mode": "walls", "color": "#F2EFE6"},
{"mode": "flooring", "image": floor}
],
});
// 3. If it is still running, check again until it finishes
while (job.status === "running") {
await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}
if (job.status === "done") {
// one per photo or reference, in order
for (const item of job.outputs) console.log(item.index, item.status, item.output ? item.output.url : item.error);
} else {
console.log("Failed:", job.error.message);
}
- Keep your key in an environment variable
Make a key on the Developers page. Every request sends it in the Authorization header.
export HOUSEKRAFT_KEY=hk_live_… - Upload your photos, one request each
The answer has asset.id: use it in the run. You can also skip this step and put an https:// image URL straight into the run.
curl -s -X POST https://app.housekraft.co/api/v1/uploads \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -F image=@room.jpg - Run En Masse
Answers straight away with the job, which keeps running.
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \ -H "Authorization: Bearer $HOUSEKRAFT_KEY" \ -H "Content-Type: application/json" \ -d '{ "image": "<asset id from POST /api/v1/uploads>", "references": [ {"mode": "walls", "color": "#8A9A5B"}, {"mode": "walls", "color": "#F2EFE6"}, {"mode": "flooring", "image": "<asset id of a floor photo>"} ] }' - Get the results
Fetch the job every few seconds until status is "done". Each result is in job.outputs, in order, with its own status; its file is at output.url. job.progress counts them.
curl -s https://app.housekraft.co/api/v1/jobs/<job id> \ -H "Authorization: Bearer $HOUSEKRAFT_KEY"
Inputs
| Field | Type | What it is |
|---|---|---|
imagerequired |
image | The room photo to work on. |
referencesrequired |
references | 2–52 things to try on the room, each {"mode": "walls", "image": "<asset id or URL>"} or {"mode": "walls", "color": "#8A9A5B"}, or a catalogue item: {"theme": "<name or id>"}, {"paint": "<name or id>"} or {"flooring_product": "<name or id>"}, or several at once from the catalogue: {"pick": "paints"|"flooring"|"themes", "count": 5, "group": "<brand or category>", "search": "<words>", "random": true}, the first matches unless random. mode is walls, flooring or themes. Each gives its own result and changes only its part: a wall colour only the walls, a floor only the floor, a theme the whole style.2–52 references |
wait | boolean | true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false. |
override_check | boolean | Has no effect: the website has no image check for this tool. |
Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.
More examples
Five colours of a paint brand, three floors of a tile brand, two themes of a category
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{"pick": "paints", "group": "Asian Paints", "count": 5},
{"pick": "flooring", "group": "Kajaria", "count": 3},
{"pick": "themes", "group": "Japandi", "count": 2}
]
}'Ten real green paint shades, picked at random
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"references": [{"pick": "paints", "search": "green", "count": 10, "random": true}]
}'Compare three themes
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{"mode": "themes", "image": "<asset id of theme 1>"},
{"mode": "themes", "image": "<asset id of theme 2>"},
{"mode": "themes", "image": "<asset id of theme 3>"}
]
}'Walls, floors and a theme in one run
curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
-H "Authorization: Bearer $HOUSEKRAFT_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{"mode": "walls", "color": "#1F3A5F"},
{"mode": "flooring", "image": "<asset id of a tile photo>"},
{"mode": "themes", "image": "<asset id of a theme photo>"}
]
}'Response
{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "enmasse",
"status": "running",
"created_at": 1793700000,
"finished_at": null,
"credits": {
"charged": 0,
"refunded": false,
"refunded_credits": 0
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{
"mode": "walls",
"color": "#8A9A5B"
},
{
"mode": "walls",
"color": "#F2EFE6"
},
{
"mode": "flooring",
"image": "<asset id of a floor photo>"
}
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"progress": {
"total": 3,
"done": 0,
"failed": 0
},
"outputs": [],
"poll_after_seconds": 3
},
"check": {
"message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
"url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "enmasse",
"status": "done",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 3,
"refunded": false,
"refunded_credits": 0
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{
"mode": "walls",
"color": "#8A9A5B"
},
{
"mode": "walls",
"color": "#F2EFE6"
},
{
"mode": "flooring",
"image": "<asset id of a floor photo>"
}
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": null,
"progress": {
"total": 3,
"done": 3,
"failed": 0
},
"outputs": [
{
"index": 0,
"reference": 0,
"mode": "walls",
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
"type": "generated",
"tool": "gridcompare",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 1,
"reference": 1,
"mode": "walls",
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6",
"type": "generated",
"tool": "gridcompare",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
},
{
"index": 2,
"reference": 2,
"mode": "flooring",
"status": "done",
"output": {
"id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7",
"type": "generated",
"tool": "gridcompare",
"url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
"thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
"width": 1024,
"height": 1024,
"created_at": 1793700025,
"kind": "image"
},
"error": null
}
],
"run_id": "71973c9dc427"
}
}{
"job": {
"id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
"tool": "enmasse",
"status": "failed",
"created_at": 1793700000,
"finished_at": 1793700025,
"credits": {
"charged": 0,
"refunded": false,
"refunded_credits": 0
},
"input": {
"image": "<asset id from POST /api/v1/uploads>",
"references": [
{
"mode": "walls",
"color": "#8A9A5B"
},
{
"mode": "walls",
"color": "#F2EFE6"
},
{
"mode": "flooring",
"image": "<asset id of a floor photo>"
}
]
},
"warnings": [],
"check_overridden": false,
"output": null,
"error": {
"message": "Model returned no image. Nothing was charged."
},
"progress": {
"total": 3,
"done": 0,
"failed": 3
},
"outputs": []
}
}Ids, links and times above are examples.
Tips
- Priced by the kinds of reference used (walls, flooring, themes), not by how many: see GET /api/v1/tools/pricing.
- Each output has reference (its place in your references list) and mode.
- Charged once the results are ready; a run that fails costs nothing.
- Every result arrives at the end of the run, together.
This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per reference (each comes back as one image); nothing if the run fails.
This tool's help as JSON →