{
  "help": {
    "id": "furniture",
    "name": "Object Placement",
    "what_it_does": "Add, replace or remove a piece of furniture.",
    "about": "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.",
    "the_photo": "A room photo with space for the piece, or showing the piece to replace or remove.",
    "credits": 2,
    "credits_label": "2 credits per run",
    "pricing": "2 credits per run, refunded if the run fails.",
    "output_kind": "image",
    "output": "One image, saved to your Library. The file is at job.output.url.",
    "response": "Answers 202 at once with the job id; fetch GET /api/v1/jobs/{id} until status is \"done\". Send \"wait\": true to wait up to 50 s for it instead.",
    "waits_for_result": false,
    "how_to_use": [
      {
        "title": "Keep your key in an environment variable",
        "detail": "Make a key on the Developers page. Every request sends it in the Authorization header.",
        "command": "export HOUSEKRAFT_KEY=hk_live_…"
      },
      {
        "title": "Upload your photo",
        "detail": "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.",
        "command": "curl -s -X POST https://app.housekraft.co/api/v1/uploads \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -F image=@room.jpg"
      },
      {
        "title": "See the choices (optional)",
        "detail": "The same list the website shows for this tool.",
        "command": "curl -s https://app.housekraft.co/api/v1/tools/furniture/options"
      },
      {
        "title": "Run Object Placement",
        "detail": "Answers straight away with the job, which keeps running.",
        "command": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"add\",\n  \"furniture_type\": \"walnut side table\",\n  \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25}\n}'"
      },
      {
        "title": "Get the result",
        "detail": "Fetch the job every few seconds until status is \"done\"; the file is at job.output.url.",
        "command": "curl -s https://app.housekraft.co/api/v1/jobs/<job id> \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\""
      }
    ],
    "inputs": [
      {
        "name": "image",
        "type": "image",
        "required": true,
        "description": "The room photo to work on.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      },
      {
        "name": "action",
        "type": "choice",
        "required": false,
        "description": "What to do.",
        "options": [
          "add",
          "replace",
          "remove"
        ],
        "free_text": false,
        "default": "add"
      },
      {
        "name": "furniture_type",
        "type": "text",
        "required": false,
        "description": "The piece, e.g. \"walnut side table\" (GET /api/v1/tools/furniture/options)."
      },
      {
        "name": "custom_prompt",
        "type": "text",
        "required": false,
        "description": "Optional. Any extra description."
      },
      {
        "name": "furniture_product",
        "type": "catalog",
        "required": false,
        "description": "Or a piece from a furniture brand in HouseKraft's catalogue, by name or id (GET /api/v1/catalog/furniture).",
        "catalog": "furniture",
        "help": "A name or id from the furniture catalogue (GET /api/v1/catalog/furniture). Several matches → the answer lists them to choose from."
      },
      {
        "name": "furniture_image",
        "type": "image",
        "required": false,
        "description": "Optional. A photo of the piece to add or swap in.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      },
      {
        "name": "position",
        "type": "box",
        "required": false,
        "description": "Optional. Where it goes. A box on the photo as percentages: {\"x\": 50, \"y\": 60, \"width\": 20, \"height\": 25} (x, y = centre)."
      }
    ],
    "options": "GET /api/v1/tools/furniture/options",
    "run_options": {
      "wait": "true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: false.",
      "override_check": "true to run even if the image check rejects the photo. Charged as usual."
    },
    "images": "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.",
    "same_as_website": "This tool runs the website's own code: the same prompt, model, settings, price and Library record as the website.",
    "example_request": {
      "title": "Add a walnut side table",
      "method": "POST",
      "url": "https://app.housekraft.co/api/v1/tools/furniture/run",
      "body": {
        "image": "<asset id from POST /api/v1/uploads>",
        "action": "add",
        "furniture_type": "walnut side table",
        "position": {
          "x": 50,
          "y": 65,
          "width": 22,
          "height": 25
        }
      }
    },
    "example_curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"add\",\n  \"furniture_type\": \"walnut side table\",\n  \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25}\n}'",
    "example_python": "# pip install requests\nimport os\nimport time\n\nimport requests\n\nAPI = \"https://app.housekraft.co\"\nHEADERS = {\"Authorization\": f\"Bearer {os.environ['HOUSEKRAFT_KEY']}\"}\n\n\ndef call(method, path, **kwargs):\n    response = requests.request(method, API + path, headers=HEADERS, timeout=90, **kwargs)\n    data = response.json()\n    if response.status_code >= 400:\n        raise RuntimeError(f\"{data['error']['code']}: {data['error']['message']}\")\n    return data\n\n\ndef upload(path):\n    with open(path, \"rb\") as f:\n        return call(\"POST\", \"/api/v1/uploads\", files={\"image\": f})[\"asset\"][\"id\"]\n\n\n# 1. Upload the photo\nphoto = upload(\"room.jpg\")\n\n# 2. Run Object Placement\njob = call(\"POST\", \"/api/v1/tools/furniture/run\", json={\n    \"image\": photo,\n    \"action\": \"add\",\n    \"furniture_type\": \"walnut side table\",\n    \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25},\n})[\"job\"]\n\n# 3. If it is still running, check again until it finishes\nwhile job[\"status\"] == \"running\":\n    time.sleep(job.get(\"poll_after_seconds\", 3))\n    job = call(\"GET\", f\"/api/v1/jobs/{job['id']}\")[\"job\"]\n\nif job[\"status\"] == \"done\":\n    print(\"Result:\", job[\"output\"][\"url\"])\nelse:\n    print(\"Failed:\", job[\"error\"][\"message\"], \"| refunded:\", job[\"credits\"][\"refunded\"])\n",
    "example_javascript": "// Node 18 or newer. Save as run.mjs, then: node run.mjs\nimport { readFile } from \"node:fs/promises\";\n\nconst API = \"https://app.housekraft.co\";\nconst HEADERS = { Authorization: `Bearer ${process.env.HOUSEKRAFT_KEY}` };\n\nasync function call(method, path, body) {\n  const options = { method, headers: { ...HEADERS } };\n  if (body instanceof FormData) {\n    options.body = body;\n  } else if (body) {\n    options.headers[\"Content-Type\"] = \"application/json\";\n    options.body = JSON.stringify(body);\n  }\n  const response = await fetch(API + path, options);\n  const data = await response.json();\n  if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);\n  return data;\n}\n\nasync function upload(path) {\n  const form = new FormData();\n  form.append(\"image\", new Blob([await readFile(path)]), path);\n  return (await call(\"POST\", \"/api/v1/uploads\", form)).asset.id;\n}\n\n// 1. Upload the photo\nconst photo = await upload(\"room.jpg\");\n\n// 2. Run Object Placement\nlet { job } = await call(\"POST\", \"/api/v1/tools/furniture/run\", {\n  \"image\": photo,\n  \"action\": \"add\",\n  \"furniture_type\": \"walnut side table\",\n  \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25},\n});\n\n// 3. If it is still running, check again until it finishes\nwhile (job.status === \"running\") {\n  await new Promise((resolve) => setTimeout(resolve, (job.poll_after_seconds || 3) * 1000));\n  ({ job } = await call(\"GET\", `/api/v1/jobs/${job.id}`));\n}\n\nif (job.status === \"done\") console.log(\"Result:\", job.output.url);\nelse console.log(\"Failed:\", job.error.message, \"| refunded:\", job.credits.refunded);\n",
    "more_examples": [
      {
        "title": "A brand piece from the catalogue",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "action": "add",
          "furniture_product": "Ekenas Armchair"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"add\",\n  \"furniture_product\": \"Ekenas Armchair\"\n}'"
      },
      {
        "title": "Replace a sofa",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "action": "replace",
          "furniture_type": "green velvet sofa",
          "position": {
            "x": 50,
            "y": 65,
            "width": 22,
            "height": 25
          }
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"replace\",\n  \"furniture_type\": \"green velvet sofa\",\n  \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25}\n}'"
      },
      {
        "title": "Remove a piece",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "action": "remove",
          "position": {
            "x": 50,
            "y": 65,
            "width": 22,
            "height": 25
          }
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"remove\",\n  \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25}\n}'"
      },
      {
        "title": "Add a piece from a product photo",
        "body": {
          "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
          }
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"add\",\n  \"furniture_image\": \"<asset id of the armchair>\",\n  \"position\": {\"x\": 50, \"y\": 65, \"width\": 22, \"height\": 25}\n}'"
      },
      {
        "title": "Let the model choose where it goes",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "action": "add",
          "furniture_type": "arched floor lamp"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/furniture/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"image\": \"<asset id from POST /api/v1/uploads>\",\n  \"action\": \"add\",\n  \"furniture_type\": \"arched floor lamp\"\n}'"
      }
    ],
    "responses": [
      {
        "title": "Started",
        "status": 202,
        "when": "The run answers like this straight away. Fetch check.url every few seconds until status is \"done\".",
        "body": {
          "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\""
          }
        }
      },
      {
        "title": "Finished",
        "status": 200,
        "when": "GET /api/v1/jobs/<id> answers like this once the job is done.",
        "body": {
          "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
          }
        }
      },
      {
        "title": "Failed",
        "status": 200,
        "when": "GET /api/v1/jobs/<id> answers like this if the model could not finish. The credits are refunded automatically.",
        "body": {
          "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."
            }
          }
        }
      }
    ],
    "checks": [
      "Image check: the website's own check of whether this photo suits this tool. If not, you get 422 image_check_failed with the reason and a fix, and no credits are used. Send the same request with \"override_check\": true to run it 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."
    ],
    "errors": [
      {
        "status": 400,
        "code": "invalid_request",
        "meaning": "A field is missing, unknown, or the wrong type. The message names the field.",
        "what_to_do": "Fix the field and send again. Nothing was charged."
      },
      {
        "status": 400,
        "code": "needs_more",
        "meaning": "The run is missing what the tool needs to be worth running (a style, a colour, a camera move, a video length…). message is the question to ask; suggestions lists good answers.",
        "what_to_do": "Ask the user, then send the run again with the answer. Nothing ran and nothing was charged."
      },
      {
        "status": 400,
        "code": "choose_one",
        "meaning": "A catalogue name matches several items. options lists them (id, name, details).",
        "what_to_do": "Ask which one, then send its name or id. Nothing was charged."
      },
      {
        "status": 400,
        "code": "not_in_catalog",
        "meaning": "No catalogue item (or category) matches that name.",
        "what_to_do": "Search the catalogue (GET /api/v1/catalog/{catalog}?search=…) and use a name or id from it."
      },
      {
        "status": 402,
        "code": "insufficient_credits",
        "meaning": "Not enough credits for this run.",
        "what_to_do": "Buy credits or upgrade your plan, then try again."
      },
      {
        "status": 404,
        "code": "image_not_found",
        "meaning": "An asset id is not in your account, or it has expired.",
        "what_to_do": "Upload the photo again with POST /api/v1/uploads."
      },
      {
        "status": 422,
        "code": "image_check_failed",
        "meaning": "The image check thinks this photo does not suit this tool. The answer has the reason (message, reasons) and a suggested fix.",
        "what_to_do": "Use a better photo, or send the same request with \"override_check\": true to run it anyway. Nothing was charged."
      },
      {
        "status": 422,
        "code": "request_not_allowed",
        "meaning": "The photo is not of a room, building or plan, or the request is about people rather than the space.",
        "what_to_do": "This cannot be overridden. Nothing was charged."
      },
      {
        "status": 429,
        "code": "too_many_running_jobs",
        "meaning": "You already have 3 jobs running.",
        "what_to_do": "Wait for one to finish, then start the next."
      },
      {
        "status": 502,
        "code": "provider_error",
        "meaning": "The image model could not be reached.",
        "what_to_do": "Try again. If credits were taken, they were refunded (the message says so)."
      }
    ],
    "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."
    ],
    "help_url": "https://app.housekraft.co/api/v1/tools/furniture/help"
  }
}
