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 a 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 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 key. Keys are made and deleted on the Developers page. A deleted key stops working at once.
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 file is at job.output.url (for tools with several results, in job.outputs). |
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)"'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
Tools that take several photos (images) or references take a list: upload each photo with its own request, then send the list of asset ids. Sending the same id twice counts as one photo.
Reuse a result
A finished job's output.id (or outputs[].output.id) is an asset id: send it to another tool to chain edits.
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. |
| 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 deleted. | Check the key, or make a new one on 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. |
| 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. |
| 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/account |
Yes | Your credits and plan. |
| 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.
Your API key, sent as "Authorization: Bearer hk_live_…". It is the same key as the API: the same paid-plan rule, the same credits and refunds, results in the same Library. Deleting the key stops the assistant too.
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_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_live_…"
}
}
}
}Add this to .vscode/mcp.json. VS Code asks for the key the first time:
{
"inputs": [
{
"type": "promptString",
"id": "housekraft-key",
"description": "HouseKraft API key",
"password": true
}
],
"servers": {
"housekraft": {
"type": "http",
"url": "https://app.housekraft.co/api/v1/mcp",
"headers": {
"Authorization": "Bearer ${input:housekraft-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_live_…"
}
}
}
}Any MCP client that speaks Streamable HTTP: this address, with this header:
https://app.housekraft.co/api/v1/mcp
Authorization: Bearer hk_live_…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.
- Quick tools wait and return the finished image, with a small preview the assistant can see. Longer ones (videos, bulk jobs, En Masse) return a job, and the assistant checks it with get_job.
- 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. |
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. |
get_tool_options | The website's own choices for a tool: wall colours, floorings, furniture, staging styles, times of day, camera angles. |
Try it from a terminal
curl -s -X POST https://app.housekraft.co/api/v1/mcp \
-H "Authorization: Bearer $HOUSEKRAFT_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_idrequired |
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_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. |
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
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_idrequired |
text | A flooring from the list (GET /api/v1/tools/flooring/options), a flooring name, or "custom_upload" together with flooring_sample. |
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. |
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
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". |
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, 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
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_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
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 - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/lighting/options - 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. The website's own choices for this tool: GET /api/v1/tools/lighting/options.
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 - See the choices (optional)
The same list the website shows for this tool.
curl -s https://app.housekraft.co/api/v1/tools/weather/options - 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. The website's own choices for this tool: GET /api/v1/tools/weather/options.
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. |
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
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 | Turn left (negative) or right (positive), in degrees.-180 to 180 · Default: 0 |
elevation |
number | Look down (negative) or up (positive), in degrees.-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. |
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
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
}'# 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,
})["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,
});
// 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 }' - 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
},
"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
},
"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
},
"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. |
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
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
- 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
- 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"}. 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
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": 2,
"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 kind of reference used (walls, flooring, themes); nothing if the run fails.
This tool's help as JSON →