HouseKraftDevelopers

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.

Base URL
https://app.housekraft.co

Accounts with an active paid plan, or that have bought credits.

The tools

Wall Paint Repaint the walls any colour, or match a paint sample.
1 credit per run Image
Flooring Lay a new floor: wood, tile, marble, vinyl or a brand sample.
1 credit per run Image
Material Swap the material on a surface, or on several marked areas.
2 credits per run Image
Object Placement Add, replace or remove a piece of furniture.
2 credits per run Image
Time of Day Change the light to any time of day.
1 credit per run Image
Weather Change the weather and the view outside the windows.
1 credit per run Image
Stage Furnish an empty room.
2 credits per run Image
Unstage Empty a room back to its shell.
1 credit per run Image
POV Re-shoot the room from another camera angle.
2 credits per run Image
AI Annotate Mark part of the photo and say what to change there.
3 credits per run Image
Floor Plan to Dollhouse Turn a floor plan into a top-down 3D render.
1 credit per run Image
360° Panorama Turn 1–6 photos of a room into a 360° panorama.
3 credits per run 360° image
Walkthrough Video Turn a room photo into a walkthrough video.
10 credits for 6 s Video
Walkthrough Video (multiple photos) One continuous walkthrough across several photos of a property.
2 credits for 6 s and 1 photo Video
Bulk Staging Stage up to 50 empty rooms in one job, in one style.
2 credits per photo Many images
Bulk Unstaging Empty up to 50 furnished rooms in one job.
1 credit per photo Many images
En Masse Try many wall colours, floors and themes on one room at once: one result for each.
1 credit per reference Many images

Getting started

Five steps from a photo to a result. Responses are formatted JSON, readable as they are.

  1. Make a key

    Sign in and make an API key on the Developers page: https://app.housekraft.co/developers. It is shown once; keep it secret.

    export HOUSEKRAFT_KEY=hk_live_…
  2. Check it works

    Shows your credits and plan.

    curl -s https://app.housekraft.co/api/v1/account -H "Authorization: Bearer $HOUSEKRAFT_KEY"
  3. 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
  4. 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"
    }'
  5. Get the result

    If the answer says "done", the link to the file is at job.output.url. If it says "running", fetch the job until it is done.

    curl -s https://app.housekraft.co/api/v1/jobs/<job id> \
      -H "Authorization: Bearer $HOUSEKRAFT_KEY"

Authentication

Send your key in the Authorization header on every request:

Authorization: Bearer hk_live_…

Every request except help, the tool list, pricing, options and health needs your API key. Keys are made and revoked on the Developers page. A revoked key stops working at once, and so do the file links it got; revoked keys stay listed there.

KeyStarts withFor
API keyhk_live_Your own code: this API.
MCP keyhk_mcp_AI assistants, through the MCP server. It does not work on the API.
Keep the key secret, as you would a password. Use it from your server, not from code that runs in a visitor's browser.

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

statusMeaning
runningStill working. Fetch again after poll_after_seconds.
doneFinished. The link to the file is at job.output.url (for tools with several results, in job.outputs). job.tips may suggest how to do even better.
failedDid 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[].statusMeaning
waitingQueued: bulk photos go to the model a few at a time.
runningBeing worked on.
doneReady at output.url.
failedCould not be finished; its credits are given back. error says why.

A bulk job where some photos failed still ends "done", with those photos marked failed. It ends "failed" only if no photo could be finished. Bulk results appear in outputs one by one while the job runs; En Masse results all arrive at the end.

Every result's link, one per line:

curl -s https://app.housekraft.co/api/v1/jobs/<job id> -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  | jq -r '.job.outputs[] | "\(.index) \(.status) \(.output.url // .error)"'

Result files

Every url and thumbnail_url (job outputs, assets) is a HouseKraft file link. It opens the file directly, with no login and no key, for 60 minutes, so you can show it in a page or download it. Keep links private: anyone with one can open it until it expires.

An expired link answers 410 link_expired: fetch the job or asset again for a fresh one. Revoking the key that got a link stops it at once.

Or fetch any of your files with your key, no link needed:

curl -s https://app.housekraft.co/api/v1/assets/<asset id>/file   -H "Authorization: Bearer $HOUSEKRAFT_KEY" -o result.jpg

To keep a file, download it: links expire, the file stays in your Library.

When a run needs more

If a run is missing what makes it worth running (a style to stage in, a colour, a camera move, a video length…), nothing runs and nothing is charged: the answer is 400 needs_more, with the question to ask (message) and good answers (suggestions).

{
  "error": {
    "code": "needs_more",
    "message": "How long should the video be (1–15 seconds)? The length sets the price.",
    "suggestions": [
      "4 seconds: …",
      "6 seconds: …",
      "10 seconds: …"
    ],
    "what_to_do": "Ask the user, then send the run again with it. Nothing ran and nothing was charged."
  }
}

A finished job can carry tips: ways the same run gets better (a brand paint from the catalogue, a photo of the exact piece…).

Images

Each image field takes an asset id or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

Upload a file

curl -s -X POST https://app.housekraft.co/api/v1/uploads \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -F image=@room.jpg

Upload from a URL

curl -s -X POST https://app.housekraft.co/api/v1/uploads \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/room.jpg"}'

Both answer with asset.id. You can also put an https:// URL straight into any image field of a run.

Several photos, or a .zip

Tools that take several photos (images) or references take a list of asset ids. Upload the photos first: one per request, several in one request (repeat the image field), or a .zip of up to 50 photos. Several files or a .zip answer with assets[] (each with its file name) and skipped[] (files that were left out, and why). Sending the same id twice counts as one photo.

curl -s -X POST https://app.housekraft.co/api/v1/uploads \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -F image=@listing.zip

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.

Catalogues

HouseKraft's catalogues, as on the website: browse them, then use an item in a run, such as staging a room in a theme or laying a brand's tile.

CatalogueWhat it holds
themesInterior design themes: a whole style to stage or restyle a room in.
materialsThe material library: stones, woods, metals, fabrics and more, for surfaces.
paintsPaint colours from paint brands, each with its colour code.
flooringTiles and floors from flooring brands.
furnitureFurniture from furniture brands.

Search a catalogue

curl -s "https://app.housekraft.co/api/v1/catalog/flooring?search=beige%20marble&per_page=5" \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY"

search matches names, brands and categories ("Kajaria beige", "japandi", "oak"). groups lists the brands (or categories) to narrow by: pass one as ?group=…. Pages hold up to 50 items; browsing is free.

curl -s https://app.housekraft.co/api/v1/catalog/paints/groups \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY"

Use an item

Each item has "use": for each tool, the inputs to add to that tool's run, besides the room photo, to apply the item. A theme has use.stage, a tile use.flooring, a paint use.walls, a material use.material, a furniture piece use.furniture. For enmasse, add use.enmasse.reference to the references list.

An item (example)

{
  "name": "Japandi Calm",
  "details": "Japandi",
  "image": "https://media.housekraft.co/…/japandi-calm.jpg",
  "thumbnail": "https://media.housekraft.co/…/japandi-calm.jpg",
  "use": {
    "stage": {
      "staging_mode": "reference",
      "reference_image": "https://media.housekraft.co/…/japandi-calm.jpg",
      "theme_name": "Japandi Calm"
    },
    "bulk_stage": {
      "reference_image": "https://media.housekraft.co/…/japandi-calm.jpg"
    },
    "dollhouse": {
      "reference_image": "https://media.housekraft.co/…/japandi-calm.jpg",
      "theme_name": "Japandi Calm"
    },
    "enmasse": {
      "reference": {
        "mode": "themes",
        "image": "https://media.housekraft.co/…/japandi-calm.jpg"
      }
    }
  }
}

Stage a room in that theme

curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "staging_mode": "reference",
  "reference_image": "https://media.housekraft.co/…/japandi-calm.jpg",
  "theme_name": "Japandi Calm"
}'

By name or id, straight in a tool

Or skip the lookup: give a catalogue item to a tool by its name or its id. A name that matches several items answers 400 choose_one with the options; one that matches nothing, not_in_catalog. Nothing is charged for either.

ToolInputCatalogueExample
stage, bulk_stage, dollhousethemethemesJapandi Calm
wallspaintpaintsSage Whisper
flooringflooring_productflooringKajaria Beige Marble
materiallibrary_materialmaterialsCalacatta Gold Marble
furniturefurniture_productfurnitureEkenas Armchair

Paint the walls a brand colour, by name

curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "paint": "Sage Whisper"
}'

A theme from a category

stage, bulk_stage and dollhouse also take theme_category (a category from GET /api/v1/catalog/themes/groups): the first theme in it is used, or a random one with "random_theme": true.

curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"images": ["<photo 1>", "<photo 2>"], "theme_category": "Scandinavian"}'

Many at once, for En Masse

En Masse references can pick many items at once: {"pick": "paints" | "flooring" | "themes", "count": N} with an optional "group" (a brand or category), "search" (e.g. "green") and "random": true. If the catalogue has fewer than N, the answer is needs_more with how many there are.

curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "references": [
    {"pick": "paints", "group": "Asian Paints", "count": 5},
    {"pick": "flooring", "group": "Kajaria", "count": 3},
    {"pick": "themes", "group": "Japandi", "count": 2}
  ]
}'

Chaining tools

Every result has an output.id. Send it as the image of the next run.

Restyle a furnished room: unstage, then stage the empty result

curl -s -X POST https://app.housekraft.co/api/v1/tools/unstage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image": "<asset id from POST /api/v1/uploads>"}'
curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<output.id from the unstage job>",
  "prompt": "Japandi, warm wood and linen"
}'

New walls and a new floor

curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "color_id": "warm white"
}'
curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<output.id from the walls job>",
  "flooring_id": "oak_hardwood"
}'

Restage a whole furnished listing: bulk unstage, then bulk stage the results

curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_unstage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"images": ["<photo 1>", "<photo 2>", "<photo 3>"]}'
curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "images": ["<outputs[0].output.id>", "<outputs[1].output.id>", "<outputs[2].output.id>"],
  "prompt": "Scandinavian, light oak"
}'

Image checks

  • Image check: whether this photo suits this tool (the website's own check). A rejection is 422 image_check_failed with the reason and a fix. Add "override_check": true to run anyway.
  • Misuse check: the photo must show a room, building or plan, and the request must be about the space, not people. A refusal is 422 request_not_allowed and cannot be overridden.
  • With several photos, every photo is checked before anything starts. If any fails, nothing runs and the error lists each photo that failed (photos[]), so you can replace just those.
  • Both checks are free: nothing is charged for a rejected request.

Credits

  • Each tool is priced exactly as on the website, from the same price table and rules.
  • Most tools take their credits when the run starts. If the run fails, the credits are refunded automatically and the job shows credits.refunded = true.
  • Bulk Staging and Bulk Unstaging take credits photo by photo, as each photo starts, and give back the credits of any photo that fails. job.credits shows what was taken (charged) and given back (refunded_credits).
  • En Masse takes its credits only once its results are ready, so a run that fails costs nothing.
  • Free: requests rejected before running (invalid inputs, the image check, the misuse check, too many running jobs), uploads, option lists, help, and checking job status as often as you like.
  • A run you push past the image check with "override_check": true is charged like any other run.
  • Credits come from your account balance: your plan's credits plus any credit packs. API and website use the same balance.
Requests a minute
120
Runs a minute
10
Jobs running at once
3
Largest image
16 MB
Photos per bulk job
50
Bulk jobs at once
1
En Masse references
2–52

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."
  }
}
HTTPcodeMeaningWhat to do
400invalid_requestA field is missing, unknown, or the wrong type. The message names the field.Fix the field and send again. Nothing was charged.
400needs_moreThe run is missing what the tool needs to be worth running (a style, a colour, a camera move, a video length…). message is the question to ask; suggestions lists good answers.Ask the user, then send the run again with the answer. Nothing ran and nothing was charged.
400choose_oneA catalogue name matches several items. options lists them (id, name, details).Ask which one, then send its name or id. Nothing was charged.
400not_in_catalogNo catalogue item (or category) matches that name.Search the catalogue (GET /api/v1/catalog/{catalog}?search=…) and use a name or id from it.
401missing_api_keyNo key in the Authorization header.Send Authorization: Bearer hk_live_…
401invalid_api_keyThe key is wrong or was revoked.Check the key, or make a new one on the Developers page.
403wrong_key_kindAn MCP key (hk_mcp_…) was sent to the API. MCP keys work only through the MCP server.Use an API key (hk_live_…) from the Developers page.
402insufficient_creditsNot enough credits for this run.Buy credits or upgrade your plan, then try again.
403paid_plan_requiredThe account has no active paid plan and has never bought credits.Subscribe or buy a credit pack.
403account_suspendedThe account is suspended.Contact HouseKraft support.
410link_expiredA file link is older than 60 minutes.Fetch the job or asset again for a fresh link.
409bulk_job_runningYou already have a bulk job running (on the website or the API).Wait for it to finish, then start the next one.
404unknown_toolNo tool with that id.See GET /api/v1/tools for the tool ids.
404image_not_foundAn asset id is not in your account, or it has expired.Upload the photo again with POST /api/v1/uploads.
404image_unavailableThe asset exists but its file could not be read.Upload the photo again.
422image_check_failedThe 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.
422request_not_allowedThe 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.
429rate_limitedToo many requests (120 a minute) or runs (10 a minute) with this key.Wait the number of seconds in the Retry-After header.
429too_many_running_jobsYou already have 3 jobs running.Wait for one to finish, then start the next.
429busyThe API is at capacity right now.Try again in about 30 seconds.
502tool_errorThe tool could not prepare this request.Try again. Nothing was charged.
502provider_errorThe image model could not be reached.Try again. If credits were taken, they were refunded (the message says so).
503check_unavailableThe safety check could not run just now.Try again in a minute. Nothing was charged.
503start_timeoutThe run took too long to start.Try again. Nothing was charged.

All endpoints

MethodPathKeyWhat it does
GET/api/v1/help NoThis guide.
GET/api/v1/tools NoEvery tool, its inputs and its price.
GET/api/v1/tools/pricing NoWhat each run costs, and how the price is worked out.
GET/api/v1/tools/{tool} NoOne tool's inputs.
GET/api/v1/tools/{tool}/help NoOne tool in full: inputs, examples in curl, Python and JavaScript, responses, tips.
GET/api/v1/tools/{tool}/options NoThe website's option list for a tool (colours, floorings, times of day…).
POST/api/v1/uploads YesUpload a photo (form field "image", or JSON {"url": "https://…"}): answers with asset.id. Several files, or a .zip of up to 50 photos, answer with assets[].
POST/api/v1/tools/{tool}/run YesRun a tool. Answers with the job.
GET/api/v1/jobs/{id} YesOne job. Fetch it until status is "done" or "failed".
GET/api/v1/jobs YesYour recent jobs (?limit=20&offset=0).
GET/api/v1/assets YesYour Library (?type=generated|uploaded|all&limit=20&offset=0).
GET/api/v1/assets/{id} YesOne Library item.
GET/api/v1/assets/{id}/file YesThe file itself, with your key (no link needed).
GET/api/v1/files/{id}?t=… NoA file link from a job or asset: opens without a key for 60 minutes.
GET/api/v1/account YesYour credits and plan.
GET/api/v1/catalog YesThe catalogues: themes, materials, and paint, flooring and furniture brands.
GET/api/v1/catalog/{catalog} YesOne page of a catalogue (?search=…&group=…&page=0&per_page=10). Each item says what to send to which tool to use it.
GET/api/v1/catalog/{catalog}/groups YesThe brands or categories a catalogue can be narrowed by (for ?group=…).
POST/api/v1/mcp YesThe MCP server, for AI assistants (Claude Code, Cursor, VS Code, Claude Desktop). See "Use with AI assistants".
GET/api/v1/health NoIs 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.

MCP server
https://app.housekraft.co/api/v1/mcp

An MCP key, sent as "Authorization: Bearer hk_mcp_…". MCP keys are separate from API keys: make one on the MCP keys page (https://app.housekraft.co/developers/mcp); an API key does not work here, and an MCP key does not work on the API. The same paid-plan rule, credits and refunds apply, and results go to the same Library. Revoking the key stops the assistant at once.

Add it to your assistant

Run this once in a terminal:

claude mcp add --transport http housekraft https://app.housekraft.co/api/v1/mcp \
  --header "Authorization: Bearer $HOUSEKRAFT_MCP_KEY"

Add this to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "housekraft": {
      "url": "https://app.housekraft.co/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer hk_mcp_…"
      }
    }
  }
}

Add this to .vscode/mcp.json. VS Code asks for the key the first time:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "housekraft-mcp-key",
      "description": "HouseKraft MCP key",
      "password": true
    }
  ],
  "servers": {
    "housekraft": {
      "type": "http",
      "url": "https://app.housekraft.co/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${input:housekraft-mcp-key}"
      }
    }
  }
}

Settings → Developer → Edit Config, then add this to claude_desktop_config.json and restart. Needs Node.js:

{
  "mcpServers": {
    "housekraft": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://app.housekraft.co/api/v1/mcp",
        "--header",
        "Authorization:${HOUSEKRAFT_AUTH}"
      ],
      "env": {
        "HOUSEKRAFT_AUTH": "Bearer hk_mcp_…"
      }
    }
  }
}

Any MCP client that speaks Streamable HTTP: this address, with this header:

https://app.housekraft.co/api/v1/mcp
Authorization: Bearer hk_mcp_…

How it works

  • Photos are given as an asset id or an https:// link. The assistant can upload a link or base64 data with upload_image, use a photo you uploaded on the website (list_library), or pass on an earlier result.
  • Photos on your computer: just name the file, or a .zip of photos for bulk staging ("stage ~/Desktop/room.jpg in Japandi style"). An assistant that can run commands, such as Claude Code, uploads them itself with a one-time link and carries on. Otherwise, or for a photo you pasted into the chat, the assistant gives you a link: drop the photos there and tell it you are done. Links work for 15 minutes, upload only, and stop if the key is revoked.
  • Quick tools wait and return the finished image, with a small preview the assistant can see, and a link to the file that opens without signing in for 60 minutes. Longer ones (videos, bulk jobs, En Masse) return a job, and the assistant checks it with get_job. If a request is missing something (a style, a colour, a video length), the assistant is told what to ask you first; nothing runs until then.
  • Name catalogue items in plain words: "paint the walls Sage Whisper", "stage it in a Japandi theme", "try 10 green paints from Asian Paints on this room". The assistant finds them in HouseKraft's catalogues and asks if a name matches several.
  • claude.ai and ChatGPT connectors sign in with OAuth, which is not offered yet.

Its tools

ToolWhat it does
wallsRepaints the walls in the photo with the colour you choose.
flooringReplaces the floor with the flooring you choose: wood, tile, marble, vinyl, or a photo of a real product.
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.
furnitureAdds a piece of furniture, swaps one piece for another, or removes a piece.
lightingRelights the room as it would look at another time of day, with the light coming through its windows.
weatherChanges the weather and the view seen through the windows: rain, snow, fog, a beach or a city outside.
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.
unstageRemoves furniture, rugs and decor, leaving the empty room.
povShows the same room from another camera angle: turn left or right, tilt up or down, zoom in or out.
editorChanges only what you point at.
dollhouseTurns a 2D floor plan into a top-down 3D "dollhouse" render of the home, furnished in the style you choose.
panoramaBuilds a 360° panorama of a room from 1 to 6 photos taken from about the same spot.
walkthroughTurns one room photo into a short video, as if walking through the room.
walkthrough_multiMakes one continuous video that moves through several photos in order: different rooms of a home, or several views of one room.
bulk_stageStages a whole listing at once: up to 50 empty rooms in one job, every room in the same style.
bulk_unstageEmpties up to 50 furnished rooms in one job: furniture, rugs and decor go, walls, floors, windows and built-ins stay.
enmasseShows one room with many options at once.
upload_imageAdd a photo to the user's HouseKraft Library and get its asset id, to use as any tool's image.
upload_filesFor photos on the user's computer, or a .zip of photos (bulk staging): returns a one-time curl command that uploads them to the user's HouseKraft Library and prints their asset ids (run it on the user's computer), and a link the user can open to drop photos in a browser instead.
get_uploadsThe photos the user uploaded through an upload link from upload_files, with their asset ids.
get_jobA run's status and result.
list_jobsThe user's recent API and MCP runs, newest first.
list_libraryThe user's HouseKraft Library, newest first: photos they uploaded (on the website or here) and results.
get_accountThe user's credit balance and plan.
browse_catalogSearch HouseKraft's catalogues: themes (whole interior styles), materials (stones, woods, metals, fabrics…), paints (paint brand colours, with colour codes), flooring (tiles and floors from flooring brands) and furniture (from furniture brands).
get_tool_optionsThe website's built-in choices for a tool: wall colours, floorings, furniture, staging modes and styles, camera angle presets.

Try it from a terminal

curl -s -X POST https://app.housekraft.co/api/v1/mcp \
  -H "Authorization: Bearer $HOUSEKRAFT_MCP_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'

Streamable HTTP (JSON-RPC over POST, plain JSON answers, no sessions). Protocol versions 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05.

Tools

Wall Paint

walls
1 credit per run Image Answers with the result

Repaints 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);
  1. 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_…
  2. 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
  3. See the choices (optional)

    The same list the website shows for this tool.

    curl -s https://app.housekraft.co/api/v1/tools/walls/options
  4. 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"
    }'
  5. 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

FieldTypeWhat it is
imagerequired image The room photo to work on.
color_id text A colour from the wall colour list (GET /api/v1/tools/walls/options), a hex code such as "#8A9A5B", or a colour or finish name such as "sage green".
paint catalog Or a brand paint colour from HouseKraft's catalogue, by name or id, e.g. "Sage Whisper" (GET /api/v1/catalog/paints).
paint_sample image Optional. A photo of the exact paint to match.
mask image Optional. A black-and-white mask: white where the walls should change.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Give at least one of: color_id, paint.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/walls/options.

More examples

A brand paint colour from the catalogue

curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "paint": "Sage Whisper"
}'

Use an exact hex colour

curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image": "<asset id from POST /api/v1/uploads>", "color_id": "#8A9A5B"}'

Match a photo of a paint swatch

curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "color_id": "Antique White",
  "paint_sample": "<asset id of a paint swatch>"
}'

Paint only some walls, using a mask

curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "color_id": "navy blue",
  "mask": "<asset id of a black-and-white mask>"
}'

Response

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

flooring
1 credit per run Image Answers with the result

Replaces 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);
  1. 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_…
  2. 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
  3. See the choices (optional)

    The same list the website shows for this tool.

    curl -s https://app.housekraft.co/api/v1/tools/flooring/options
  4. 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"
    }'
  5. 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

FieldTypeWhat it is
imagerequired image The room photo to work on.
flooring_id text A flooring from the list (GET /api/v1/tools/flooring/options), a flooring name, or "custom_upload" together with flooring_sample.
flooring_product catalog Or a tile or floor from a brand in HouseKraft's catalogue, by name or id, e.g. "Kajaria Beige Marble" (GET /api/v1/catalog/flooring).
flooring_sample image Optional. A photo of the tile or flooring to lay.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Give at least one of: flooring_id, flooring_product.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/flooring/options.

More examples

A brand tile from the catalogue

curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "flooring_product": "Kajaria Beige Marble"
}'

Lay your own tile from a photo

curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "flooring_id": "custom_upload",
  "flooring_sample": "<asset id of a tile photo>"
}'

Describe the flooring by name

curl -s -X POST https://app.housekraft.co/api/v1/tools/flooring/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "flooring_id": "herringbone oak"
}'

Response

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

material
2 credits per run Image Answers with a job to check

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.

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);
  1. 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_…
  2. 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
  3. 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"
    }'
  4. 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

FieldTypeWhat it is
imagerequired image The room photo to work on.
material_id text Optional. A material id or name, e.g. "white_marble".
library_material catalog Or a material from HouseKraft's material library, by name or id, e.g. "Calacatta Gold Marble" (GET /api/v1/catalog/materials).
custom_prompt text Optional. Describe the material or where it goes.
material_image image Optional. A photo of the material.
placements placements Optional. Several areas, each with its own material: a list of {"name": "oak", "image": "<asset id or URL>", "position": {"x": 30, "y": 70, "width": 25, "height": 20}}. Each needs a name or an image. Positions are percentages of the photo (x, y = centre).Up to 8
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Give at least one of: material_id, library_material, custom_prompt, placements.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

More examples

A material from the library

curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "library_material": "Calacatta Gold Marble",
  "custom_prompt": "the kitchen countertop"
}'

Describe the material in words

curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "custom_prompt": "dark green zellige tiles on the backsplash"
}'

Use a photo of the material

curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "custom_prompt": "the kitchen island",
  "material_image": "<asset id of a material photo>"
}'

Several areas at once, each with its own material

curl -s -X POST https://app.housekraft.co/api/v1/tools/material/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "placements": [
    {"name": "oak", "position": {"x": 30, "y": 75, "width": 30, "height": 20}},
    {"name": "brass", "image": "<asset id of a brass swatch>", "position": {"x": 70, "y": 40, "width": 15, "height": 15}}
  ]
}'

Response

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

furniture
2 credits per run Image Answers with a job to check

Adds 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);
  1. 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_…
  2. 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
  3. See the choices (optional)

    The same list the website shows for this tool.

    curl -s https://app.housekraft.co/api/v1/tools/furniture/options
  4. 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}
    }'
  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

FieldTypeWhat it is
imagerequired image The room photo to work on.
action choice What to do.One of: add, replace, remove · Default: add
furniture_type text The piece, e.g. "walnut side table" (GET /api/v1/tools/furniture/options).
custom_prompt text Optional. Any extra description.
furniture_product catalog Or a piece from a furniture brand in HouseKraft's catalogue, by name or id (GET /api/v1/catalog/furniture).
furniture_image image Optional. A photo of the piece to add or swap in.
position box Optional. Where it goes. A box on the photo as percentages: {"x": 50, "y": 60, "width": 20, "height": 25} (x, y = centre).
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/furniture/options.

More examples

A brand piece from the catalogue

curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "action": "add",
  "furniture_product": "Ekenas Armchair"
}'

Replace a sofa

curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "action": "replace",
  "furniture_type": "green velvet sofa",
  "position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'

Remove a piece

curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "action": "remove",
  "position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'

Add a piece from a product photo

curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "action": "add",
  "furniture_image": "<asset id of the armchair>",
  "position": {"x": 50, "y": 65, "width": 22, "height": 25}
}'

Let the model choose where it goes

curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "action": "add",
  "furniture_type": "arched floor lamp"
}'

Response

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

lighting
1 credit per run Image Answers with the result

Relights 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);
  1. 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_…
  2. 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
  3. 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"
    }'
  4. 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

FieldTypeWhat 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.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

More examples

Night, with the lamps on

curl -s -X POST https://app.housekraft.co/api/v1/tools/lighting/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "time_of_day": "night",
  "custom_prompt": "warm lamps on"
}'

Early morning light

curl -s -X POST https://app.housekraft.co/api/v1/tools/lighting/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "time_of_day": "sunrise"
}'

Response

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

weather
1 credit per run Image Answers with the result

Changes 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);
  1. 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_…
  2. 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
  3. 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"}'
  4. 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

FieldTypeWhat 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.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Give at least one of: weather, location, custom_prompt, time_of_day.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

More examples

Rain in Lisbon

curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "weather": "rainy",
  "location": "Lisbon"
}'

A beach at sunset

curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "location": "beach",
  "time_of_day": "sunset"
}'

Describe it in your own words

curl -s -X POST https://app.housekraft.co/api/v1/tools/weather/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "custom_prompt": "light fog over a pine forest"
}'

Response

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

stage
2 credits per run Image Answers with the result

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.

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);
  1. 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_…
  2. 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
  3. See the choices (optional)

    The same list the website shows for this tool.

    curl -s https://app.housekraft.co/api/v1/tools/stage/options
  4. 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"
    }'
  5. 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

FieldTypeWhat it is
imagerequired image The empty room to furnish.
prompt text Optional. Style or anything specific, e.g. "Scandinavian, light oak".
staging_mode choice How to stage: from the prompt, from a reference photo, or with specific furniture photos.One of: prompt_only, reference, furniture · Default: prompt_only
reference_image image For staging_mode "reference": a style reference or moodboard.
furniture_images images For staging_mode "furniture": photos of the pieces to use.1–6 images
theme_name text Optional. The name of a theme, if reference_image is one.
theme catalog Or a theme from HouseKraft's catalogue, by name or id, e.g. "Japandi Calm" (GET /api/v1/catalog/themes): the room is staged in it.
theme_category catalog_group Or a theme category (GET /api/v1/catalog/themes/groups), e.g. "Japandi": one theme from it is used, the first, or a random one with random_theme.
random_theme boolean With theme_category: pick the theme at random instead of the first.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB. The website's own choices for this tool: GET /api/v1/tools/stage/options.

More examples

Stage in a catalogue theme

curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "theme": "Japandi Calm"
}'

Stage in a random theme from a category

curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "theme_category": "Japandi",
  "random_theme": true
}'

Copy the style of a moodboard

curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "staging_mode": "reference",
  "reference_image": "<asset id of a moodboard>"
}'

Use these exact pieces

curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "staging_mode": "furniture",
  "furniture_images": ["<asset id of a sofa>", "<asset id of a lamp>"]
}'

Use a named theme photo

curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "staging_mode": "reference",
  "reference_image": "<asset id of a theme photo>",
  "theme_name": "Japandi"
}'

Response

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

unstage
1 credit per run Image Answers with the result

Removes 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);
  1. 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_…
  2. 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
  3. 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>"}'
  4. 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

FieldTypeWhat it is
imagerequired image The furnished room to empty.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue 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

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

pov
2 credits per run Image Answers with a job to check

Shows 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);
  1. 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_…
  2. 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
  3. See the choices (optional)

    The same list the website shows for this tool.

    curl -s https://app.housekraft.co/api/v1/tools/pov/options
  4. 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
    }'
  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

FieldTypeWhat it is
imagerequired image The room photo to work on.
azimuth number Move the camera around the room, in degrees: negative to the left, positive to the right (90 = the right side, 180 = from behind).-180 to 180 · Default: 0
elevation number Camera height, in degrees: positive raises the camera to look down on the room (60 = bird's eye, 90 = straight down); negative lowers it to look up.-30 to 90 · Default: 0
zoom number Zoom, 0 (wide) to 10 (close).0 to 10 · Default: 5
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue 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

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

editor
3 credits per run Image Answers with a job to check

Changes 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);
  1. 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_…
  2. 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
  3. 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"
    }'
  4. 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

FieldTypeWhat 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
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue 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

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

dollhouse
1 credit per run Image Answers with the result

Turns 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);
  1. 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_…
  2. 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
  3. 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"}'
  4. 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

FieldTypeWhat it is
imagerequired image The floor plan.
style choice Interior style.One of: modern, scandinavian, luxury, mediterranean, industrial, japandi · Default: modern
custom_style text Optional. Describe the style in your own words.
reference_image image Optional. A style reference or theme photo.
theme_name text Optional. The theme's name, if reference_image is one.
theme catalog Or a theme from HouseKraft's catalogue, by name or id (GET /api/v1/catalog/themes).
theme_category catalog_group Or a theme category (GET /api/v1/catalog/themes/groups), e.g. "Japandi": one theme from it is used, the first, or a random one with random_theme.
random_theme boolean With theme_category: pick the theme at random instead of the first.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

More examples

Furnish it in a catalogue theme

curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "theme": "Japandi Calm"
}'

Describe the style in your own words

curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id of a floor plan>",
  "custom_style": "warm terracotta and rattan"
}'

Use a theme photo

curl -s -X POST https://app.housekraft.co/api/v1/tools/dollhouse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id of a floor plan>",
  "reference_image": "<asset id of a theme photo>",
  "theme_name": "Japandi"
}'

Response

The run answers like this when the result is ready within 50 s. GET /api/v1/jobs/<id> answers the same once the job is done.
{
  "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
  }
}
The run answers like this if the result takes longer than 50 s (or you sent "wait": false). Fetch check.url until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

panorama
3 credits per run 360° panorama Answers with a job to check

Builds 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);
  1. 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_…
  2. 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
  3. 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>"]}'
  4. 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

FieldTypeWhat it is
imagesrequired images 1–6 photos of the same room, taken from roughly one spot. Order does not matter.1–6 images
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue 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

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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

walkthrough
10 credits for 6 s MP4 video Answers with a job to check

Turns 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);
  1. 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_…
  2. 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
  3. 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}'
  4. 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

FieldTypeWhat it is
imagerequired image The room photo to work on.
duration integer Video length in seconds.1 to 15 · Default: 6
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue 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

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "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_multi
2 credits for 6 s and 1 photo MP4 video Answers with a job to check

Makes one continuous video that moves through several photos in order: different rooms of a home, or several views of one room.

Photo: 1 to 7 photos, in the order the camera should visit them.

Example: A walkthrough of a home from three photos

curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "images": ["<front of house>", "<hallway>", "<living room>"],
  "duration": 8,
  "scope": "property"
}'
# pip install requests
import os
import time

import requests

API = "https://app.housekraft.co"
HEADERS = {"Authorization": f"Bearer {os.environ['HOUSEKRAFT_KEY']}"}


def call(method, path, **kwargs):
    response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)
    data = response.json()
    if response.status_code >= 400:
        raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
    return data


def upload(path):
    with open(path, "rb") as f:
        return call("POST", "/api/v1/uploads", files={"image": f})["asset"]["id"]


# 1. Upload the photos
photos = [upload(path) for path in ["front-of-house.jpg", "hallway.jpg", "living-room.jpg"]]

# 2. Run Walkthrough Video (multiple photos)
job = call("POST", "/api/v1/tools/walkthrough_multi/run", json={
    "images": photos,
    "duration": 8,
    "scope": "property",
})["job"]

# 3. If it is still running, check again until it finishes
while job["status"] == "running":
    time.sleep(job.get("poll_after_seconds", 3))
    job = call("GET", f"/api/v1/jobs/{job['id']}")["job"]

if job["status"] == "done":
    print("Result:", job["output"]["url"])
else:
    print("Failed:", job["error"]["message"], "| refunded:", job["credits"]["refunded"])
// Node 18 or newer. Save as run.mjs, then: node run.mjs
import { readFile } from "node:fs/promises";

const API = "https://app.housekraft.co";
const HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };

async function call(method, path, body) {
  const options = { method, headers: { ...HEADERS } };
  if (body instanceof FormData) {
    options.body = body;
  } else if (body) {
    options.headers["Content-Type"] = "application/json";
    options.body = JSON.stringify(body);
  }
  const response = await fetch(API + path, options);
  const data = await response.json();
  if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
  return data;
}

async function upload(path) {
  const form = new FormData();
  form.append("image", new Blob([await readFile(path)]), path);
  return (await call("POST", "/api/v1/uploads", form)).asset.id;
}

// 1. Upload the photos
const photos = await Promise.all(["front-of-house.jpg", "hallway.jpg", "living-room.jpg"].map(upload));

// 2. Run Walkthrough Video (multiple photos)
let { job } = await call("POST", "/api/v1/tools/walkthrough_multi/run", {
  "images": photos,
  "duration": 8,
  "scope": "property",
});

// 3. If it is still running, check again until it finishes
while (job.status === "running") {
  await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));
  ({ job } = await call("GET", `/api/v1/jobs/${job.id}`));
}

if (job.status === "done") console.log("Result:", job.output.url);
else console.log("Failed:", job.error.message, "| refunded:", job.credits.refunded);
  1. 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_…
  2. 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
  3. Run Walkthrough Video (multiple photos)

    Answers straight away with the job, which keeps running.

    curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \
      -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "images": ["<front of house>", "<hallway>", "<living room>"],
      "duration": 8,
      "scope": "property"
    }'
  4. 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

FieldTypeWhat 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
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue 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

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "walkthrough_multi",
    "status": "running",
    "created_at": 1793700000,
    "finished_at": null,
    "credits": {
      "charged": 2,
      "refunded": false
    },
    "input": {
      "images": [
        "<front of house>",
        "<hallway>",
        "<living room>"
      ],
      "duration": 8,
      "scope": "property"
    },
    "warnings": [],
    "check_overridden": false,
    "output": null,
    "error": null,
    "poll_after_seconds": 3
  },
  "check": {
    "message": "Started. The job carries on; get the result from this URL. Nothing is lost if this connection closes, and GET /api/v1/jobs lists your recent jobs.",
    "url": "https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "command": "curl -s https://app.housekraft.co/api/v1/jobs/7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8 -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "walkthrough_multi",
    "status": "done",
    "created_at": 1793700000,
    "finished_at": 1793700025,
    "credits": {
      "charged": 2,
      "refunded": false
    },
    "input": {
      "images": [
        "<front of house>",
        "<hallway>",
        "<living room>"
      ],
      "duration": 8,
      "scope": "property"
    },
    "warnings": [],
    "check_overridden": false,
    "output": {
      "id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
      "type": "generated",
      "tool": "walkthrough",
      "url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
      "thumbnail_url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
      "width": null,
      "height": null,
      "created_at": 1793700025,
      "kind": "video"
    },
    "error": null
  }
}
GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "walkthrough_multi",
    "status": "failed",
    "created_at": 1793700000,
    "finished_at": 1793700025,
    "credits": {
      "charged": 2,
      "refunded": true
    },
    "input": {
      "images": [
        "<front of house>",
        "<hallway>",
        "<living room>"
      ],
      "duration": 8,
      "scope": "property"
    },
    "warnings": [],
    "check_overridden": false,
    "output": null,
    "error": {
      "message": "The image model could not finish this request."
    }
  }
}

Ids, links and times above are examples.

Tips

  • List the photos in the order the camera should visit them.
  • The price depends on the length and the number of photos: see GET /api/v1/tools/pricing.

This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. Priced by video length and number of photos; see GET /api/v1/tools/pricing. Refunded if the run fails.

This tool's help as JSON →

Bulk Staging

bulk_stage
2 credits per photo One image per photo or reference Answers with a job to check

Stages 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);
}
  1. 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_…
  2. 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
  3. 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"
    }'
  4. 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

FieldTypeWhat it is
imagesrequired images 1–50 photos of empty rooms. Each gets its own result.1–50 images
prompt text Optional. The style for every room, e.g. "Scandinavian, light oak".
reference_image image Optional. A theme or style photo: every room is staged in its style.
theme catalog Or a theme from HouseKraft's catalogue, by name or id (GET /api/v1/catalog/themes): every room is staged in it.
theme_category catalog_group Or a theme category (GET /api/v1/catalog/themes/groups), e.g. "Japandi": one theme from it is used, the first, or a random one with random_theme.
random_theme boolean With theme_category: pick the theme at random instead of the first.
sku text Optional. A listing id saved on every result, to find them later (letters, numbers, - _ .). One is made up if you leave it out.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue to run even if the image check rejects the photo. Charged as usual.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

More examples

Every room in a catalogue theme

curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "images": ["<photo 1>", "<photo 2>", "<photo 3>"],
  "theme": "Japandi Calm"
}'

Every room in a theme from a category

curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"images": ["<photo 1>", "<photo 2>"], "theme_category": "Scandinavian"}'

Stage every room in the style of a theme photo

curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "images": ["<photo 1>", "<photo 2>"],
  "reference_image": "<asset id of a theme photo>"
}'

Tag the results with a listing id

curl -s -X POST https://app.housekraft.co/api/v1/tools/bulk_stage/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "images": ["<photo 1>", "<photo 2>"],
  "prompt": "modern farmhouse",
  "sku": "MLS-2291"
}'

Response

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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"
  }
}
GET /api/v1/jobs/<id> answers like this if no photo could be finished. Each photo's credits are refunded automatically; a job where only some failed is "done", with those photos marked failed in outputs.
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "bulk_stage",
    "status": "failed",
    "created_at": 1793700000,
    "finished_at": 1793700025,
    "credits": {
      "charged": 0,
      "refunded": true,
      "refunded_credits": 6
    },
    "input": {
      "images": [
        "<photo 1>",
        "<photo 2>",
        "<photo 3>"
      ],
      "prompt": "Scandinavian, light oak and linen"
    },
    "warnings": [],
    "check_overridden": false,
    "output": null,
    "error": {
      "message": "None of the photos could be finished. Their credits have been refunded."
    },
    "progress": {
      "total": 3,
      "done": 0,
      "failed": 3
    },
    "outputs": [
      {
        "index": 0,
        "status": "failed",
        "output": null,
        "error": "The image model could not finish this photo."
      },
      {
        "index": 1,
        "status": "failed",
        "output": null,
        "error": "The image model could not finish this photo."
      },
      {
        "index": 2,
        "status": "failed",
        "output": null,
        "error": "The image model could not finish this photo."
      }
    ],
    "sku": "HK-3F9A1C2B"
  }
}

Ids, links and times above are examples.

Tips

  • Have the photos in a .zip? Upload it to POST /api/v1/uploads and send the asset ids it answers with (assets[].id).
  • Each photo is charged when it starts and refunded if it fails: job.credits shows the total.
  • job.outputs lists every photo in the order you sent them, each with its own status and result.
  • One bulk job at a time per account, on the website and the API together.
  • The batch also shows in Bulk on the website.

This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 2 credits per photo, each refunded if that photo fails.

This tool's help as JSON →

Bulk Unstaging

bulk_unstage
1 credit per photo One image per photo or reference Answers with a job to check

Empties 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);
}
  1. 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_…
  2. 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
  3. 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>"]}'
  4. 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

FieldTypeWhat 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.
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleantrue 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

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "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"
  }
}
GET /api/v1/jobs/<id> answers like this if no photo could be finished. Each photo's credits are refunded automatically; a job where only some failed is "done", with those photos marked failed in outputs.
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "bulk_unstage",
    "status": "failed",
    "created_at": 1793700000,
    "finished_at": 1793700025,
    "credits": {
      "charged": 0,
      "refunded": true,
      "refunded_credits": 3
    },
    "input": {
      "images": [
        "<photo 1>",
        "<photo 2>",
        "<photo 3>"
      ]
    },
    "warnings": [],
    "check_overridden": false,
    "output": null,
    "error": {
      "message": "None of the photos could be finished. Their credits have been refunded."
    },
    "progress": {
      "total": 3,
      "done": 0,
      "failed": 3
    },
    "outputs": [
      {
        "index": 0,
        "status": "failed",
        "output": null,
        "error": "The image model could not finish this photo."
      },
      {
        "index": 1,
        "status": "failed",
        "output": null,
        "error": "The image model could not finish this photo."
      },
      {
        "index": 2,
        "status": "failed",
        "output": null,
        "error": "The image model could not finish this photo."
      }
    ],
    "sku": "HK-3F9A1C2B"
  }
}

Ids, links and times above are examples.

Tips

  • Have the photos in a .zip? Upload it to POST /api/v1/uploads and send the asset ids it answers with (assets[].id).
  • To restage a furnished listing: bulk_unstage, then bulk_stage the results (their output.id).
  • Each photo is charged when it starts and refunded if it fails: job.credits shows the total.
  • One bulk job at a time per account, on the website and the API together.

This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per photo, each refunded if that photo fails.

This tool's help as JSON →

En Masse

enmasse
1 credit per reference One image per photo or reference Answers with a job to check

Shows 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);
}
  1. 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_…
  2. 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
  3. 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>"}
      ]
    }'
  4. 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

FieldTypeWhat it is
imagerequired image The room photo to work on.
referencesrequired references 2–52 things to try on the room, each {"mode": "walls", "image": "<asset id or URL>"} or {"mode": "walls", "color": "#8A9A5B"}, or a catalogue item: {"theme": "<name or id>"}, {"paint": "<name or id>"} or {"flooring_product": "<name or id>"}, or several at once from the catalogue: {"pick": "paints"|"flooring"|"themes", "count": 5, "group": "<brand or category>", "search": "<words>", "random": true}, the first matches unless random. mode is walls, flooring or themes. Each gives its own result and changes only its part: a wall colour only the walls, a floor only the floor, a theme the whole style.2–52 references
waitbooleantrue or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.
override_checkbooleanHas no effect: the website has no image check for this tool.

Each image field takes an asset id (from POST /api/v1/uploads, or an earlier result's output.id) or an https:// URL. JPG, PNG or WEBP, up to 16 MB.

More examples

Five colours of a paint brand, three floors of a tile brand, two themes of a category

curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "references": [
    {"pick": "paints", "group": "Asian Paints", "count": 5},
    {"pick": "flooring", "group": "Kajaria", "count": 3},
    {"pick": "themes", "group": "Japandi", "count": 2}
  ]
}'

Ten real green paint shades, picked at random

curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "references": [{"pick": "paints", "search": "green", "count": 10, "random": true}]
}'

Compare three themes

curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "references": [
    {"mode": "themes", "image": "<asset id of theme 1>"},
    {"mode": "themes", "image": "<asset id of theme 2>"},
    {"mode": "themes", "image": "<asset id of theme 3>"}
  ]
}'

Walls, floors and a theme in one run

curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/run \
  -H "Authorization: Bearer $HOUSEKRAFT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "image": "<asset id from POST /api/v1/uploads>",
  "references": [
    {"mode": "walls", "color": "#1F3A5F"},
    {"mode": "flooring", "image": "<asset id of a tile photo>"},
    {"mode": "themes", "image": "<asset id of a theme photo>"}
  ]
}'

Response

The run answers like this straight away. Fetch check.url every few seconds until status is "done".
{
  "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\""
  }
}
GET /api/v1/jobs/<id> answers like this once the job is done.
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "enmasse",
    "status": "done",
    "created_at": 1793700000,
    "finished_at": 1793700025,
    "credits": {
      "charged": 3,
      "refunded": false,
      "refunded_credits": 0
    },
    "input": {
      "image": "<asset id from POST /api/v1/uploads>",
      "references": [
        {
          "mode": "walls",
          "color": "#8A9A5B"
        },
        {
          "mode": "walls",
          "color": "#F2EFE6"
        },
        {
          "mode": "flooring",
          "image": "<asset id of a floor photo>"
        }
      ]
    },
    "warnings": [],
    "check_overridden": false,
    "output": null,
    "error": null,
    "progress": {
      "total": 3,
      "done": 3,
      "failed": 0
    },
    "outputs": [
      {
        "index": 0,
        "reference": 0,
        "mode": "walls",
        "status": "done",
        "output": {
          "id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
          "type": "generated",
          "tool": "gridcompare",
          "url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
          "thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.jpg",
          "width": 1024,
          "height": 1024,
          "created_at": 1793700025,
          "kind": "image"
        },
        "error": null
      },
      {
        "index": 1,
        "reference": 1,
        "mode": "walls",
        "status": "done",
        "output": {
          "id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6",
          "type": "generated",
          "tool": "gridcompare",
          "url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
          "thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b6.jpg",
          "width": 1024,
          "height": 1024,
          "created_at": 1793700025,
          "kind": "image"
        },
        "error": null
      },
      {
        "index": 2,
        "reference": 2,
        "mode": "flooring",
        "status": "done",
        "output": {
          "id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7",
          "type": "generated",
          "tool": "gridcompare",
          "url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
          "thumbnail_url": "https://…/thumbnails/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b7.jpg",
          "width": 1024,
          "height": 1024,
          "created_at": 1793700025,
          "kind": "image"
        },
        "error": null
      }
    ],
    "run_id": "71973c9dc427"
  }
}
GET /api/v1/jobs/<id> answers like this if the run could not finish. En Masse charges only once the results are ready, so nothing was taken.
{
  "job": {
    "id": "7c1e9b02-5d4a-4e8f-b6a3-2f90d1c4e5a8",
    "tool": "enmasse",
    "status": "failed",
    "created_at": 1793700000,
    "finished_at": 1793700025,
    "credits": {
      "charged": 0,
      "refunded": false,
      "refunded_credits": 0
    },
    "input": {
      "image": "<asset id from POST /api/v1/uploads>",
      "references": [
        {
          "mode": "walls",
          "color": "#8A9A5B"
        },
        {
          "mode": "walls",
          "color": "#F2EFE6"
        },
        {
          "mode": "flooring",
          "image": "<asset id of a floor photo>"
        }
      ]
    },
    "warnings": [],
    "check_overridden": false,
    "output": null,
    "error": {
      "message": "Model returned no image. Nothing was charged."
    },
    "progress": {
      "total": 3,
      "done": 0,
      "failed": 3
    },
    "outputs": []
  }
}

Ids, links and times above are examples.

Tips

  • Priced by the kinds of reference used (walls, flooring, themes), not by how many: see GET /api/v1/tools/pricing.
  • Each output has reference (its place in your references list) and mode.
  • Charged once the results are ready; a run that fails costs nothing.
  • Every result arrives at the end of the run, together.

This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website. 1 credit per reference (each comes back as one image); nothing if the run fails.

This tool's help as JSON →