{
  "help": {
    "id": "stage",
    "name": "Stage",
    "what_it_does": "Furnish an empty room.",
    "about": "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.",
    "the_photo": "An empty room. For a furnished room, run unstage first and stage the result.",
    "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": "Waits for the result (up to 50 s) and answers 200 with the finished job. If it takes longer, the answer is 202 with check.url to fetch it from. Send \"wait\": false to get the job id straight away.",
    "waits_for_result": true,
    "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/stage/options"
      },
      {
        "title": "Run Stage",
        "detail": "Waits for the result (up to 50 s) and answers with the finished job.",
        "command": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"prompt\": \"Scandinavian, light oak and linen\"\n}'"
      },
      {
        "title": "Get the result",
        "detail": "If the answer says \"done\", the file is at job.output.url. If it says \"running\", fetch the job until it is done.",
        "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 empty room to furnish.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      },
      {
        "name": "prompt",
        "type": "text",
        "required": false,
        "description": "Optional. Style or anything specific, e.g. \"Scandinavian, light oak\"."
      },
      {
        "name": "staging_mode",
        "type": "choice",
        "required": false,
        "description": "How to stage: from the prompt, from a reference photo, or with specific furniture photos.",
        "options": [
          "prompt_only",
          "reference",
          "furniture"
        ],
        "free_text": false,
        "default": "prompt_only"
      },
      {
        "name": "reference_image",
        "type": "image",
        "required": false,
        "description": "For staging_mode \"reference\": a style reference or moodboard.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      },
      {
        "name": "furniture_images",
        "type": "images",
        "required": false,
        "description": "For staging_mode \"furniture\": photos of the pieces to use.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL.",
        "min_items": 1,
        "max_items": 6
      },
      {
        "name": "theme_name",
        "type": "text",
        "required": false,
        "description": "Optional. The name of a theme, if reference_image is one."
      },
      {
        "name": "theme",
        "type": "catalog",
        "required": false,
        "description": "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.",
        "catalog": "themes",
        "help": "A name or id from the themes catalogue (GET /api/v1/catalog/themes). Several matches → the answer lists them to choose from."
      },
      {
        "name": "theme_category",
        "type": "catalog_group",
        "required": false,
        "description": "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.",
        "catalog": "themes",
        "help": "A category name from GET /api/v1/catalog/themes/groups."
      },
      {
        "name": "random_theme",
        "type": "boolean",
        "required": false,
        "description": "With theme_category: pick the theme at random instead of the first."
      }
    ],
    "options": "GET /api/v1/tools/stage/options",
    "run_options": {
      "wait": "true or false. Whether the run request waits for the result (up to 50 s). Default for this tool: true.",
      "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": "Stage in a Scandinavian style",
      "method": "POST",
      "url": "https://app.housekraft.co/api/v1/tools/stage/run",
      "body": {
        "image": "<asset id from POST /api/v1/uploads>",
        "prompt": "Scandinavian, light oak and linen"
      }
    },
    "example_curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"prompt\": \"Scandinavian, light oak and linen\"\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 Stage\njob = call(\"POST\", \"/api/v1/tools/stage/run\", json={\n    \"image\": photo,\n    \"prompt\": \"Scandinavian, light oak and linen\",\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 Stage\nlet { job } = await call(\"POST\", \"/api/v1/tools/stage/run\", {\n  \"image\": photo,\n  \"prompt\": \"Scandinavian, light oak and linen\",\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": "Stage in a catalogue theme",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "theme": "Japandi Calm"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"theme\": \"Japandi Calm\"\n}'"
      },
      {
        "title": "Stage in a random theme from a category",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "theme_category": "Japandi",
          "random_theme": true
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"theme_category\": \"Japandi\",\n  \"random_theme\": true\n}'"
      },
      {
        "title": "Copy the style of a moodboard",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "staging_mode": "reference",
          "reference_image": "<asset id of a moodboard>"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"staging_mode\": \"reference\",\n  \"reference_image\": \"<asset id of a moodboard>\"\n}'"
      },
      {
        "title": "Use these exact pieces",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "staging_mode": "furniture",
          "furniture_images": [
            "<asset id of a sofa>",
            "<asset id of a lamp>"
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"staging_mode\": \"furniture\",\n  \"furniture_images\": [\"<asset id of a sofa>\", \"<asset id of a lamp>\"]\n}'"
      },
      {
        "title": "Use a named theme photo",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "staging_mode": "reference",
          "reference_image": "<asset id of a theme photo>",
          "theme_name": "Japandi"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/stage/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  \"staging_mode\": \"reference\",\n  \"reference_image\": \"<asset id of a theme photo>\",\n  \"theme_name\": \"Japandi\"\n}'"
      }
    ],
    "responses": [
      {
        "title": "Finished",
        "status": 200,
        "when": "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.",
        "body": {
          "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
          }
        }
      },
      {
        "title": "Still running",
        "status": 202,
        "when": "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\".",
        "body": {
          "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\""
          }
        }
      },
      {
        "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": "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."
            }
          }
        }
      }
    ],
    "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": [
      "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."
    ],
    "help_url": "https://app.housekraft.co/api/v1/tools/stage/help"
  }
}
