{
  "help": {
    "id": "walls",
    "name": "Wall Paint",
    "what_it_does": "Repaint the walls any colour, or match a paint sample.",
    "about": "Repaints the walls in the photo with the colour you choose. Everything else in the room stays as it is.",
    "the_photo": "A room photo where the walls are clearly visible.",
    "credits": 1,
    "credits_label": "1 credit per run",
    "pricing": "1 credit 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/walls/options"
      },
      {
        "title": "Run Wall Paint",
        "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/walls/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  \"color_id\": \"sage green\"\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 room photo to work on.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      },
      {
        "name": "color_id",
        "type": "text",
        "required": false,
        "description": "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\"."
      },
      {
        "name": "paint",
        "type": "catalog",
        "required": false,
        "description": "Or a brand paint colour from HouseKraft's catalogue, by name or id, e.g. \"Sage Whisper\" (GET /api/v1/catalog/paints).",
        "catalog": "paints",
        "help": "A name or id from the paints catalogue (GET /api/v1/catalog/paints). Several matches → the answer lists them to choose from."
      },
      {
        "name": "paint_sample",
        "type": "image",
        "required": false,
        "description": "Optional. A photo of the exact paint to match.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      },
      {
        "name": "mask",
        "type": "image",
        "required": false,
        "description": "Optional. A black-and-white mask: white where the walls should change.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL."
      }
    ],
    "required_one_of": [
      "color_id",
      "paint"
    ],
    "options": "GET /api/v1/tools/walls/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": "Paint the walls sage green",
      "method": "POST",
      "url": "https://app.housekraft.co/api/v1/tools/walls/run",
      "body": {
        "image": "<asset id from POST /api/v1/uploads>",
        "color_id": "sage green"
      }
    },
    "example_curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/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  \"color_id\": \"sage green\"\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 Wall Paint\njob = call(\"POST\", \"/api/v1/tools/walls/run\", json={\n    \"image\": photo,\n    \"color_id\": \"sage green\",\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 Wall Paint\nlet { job } = await call(\"POST\", \"/api/v1/tools/walls/run\", {\n  \"image\": photo,\n  \"color_id\": \"sage green\",\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 paint colour from the catalogue",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "paint": "Sage Whisper"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/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  \"paint\": \"Sage Whisper\"\n}'"
      },
      {
        "title": "Use an exact hex colour",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "color_id": "#8A9A5B"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"image\": \"<asset id from POST /api/v1/uploads>\", \"color_id\": \"#8A9A5B\"}'"
      },
      {
        "title": "Match a photo of a paint swatch",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "color_id": "Antique White",
          "paint_sample": "<asset id of a paint swatch>"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/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  \"color_id\": \"Antique White\",\n  \"paint_sample\": \"<asset id of a paint swatch>\"\n}'"
      },
      {
        "title": "Paint only some walls, using a mask",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "color_id": "navy blue",
          "mask": "<asset id of a black-and-white mask>"
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walls/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  \"color_id\": \"navy blue\",\n  \"mask\": \"<asset id of a black-and-white mask>\"\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": "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
          }
        }
      },
      {
        "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": "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\""
          }
        }
      },
      {
        "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": "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."
            }
          }
        }
      }
    ],
    "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": [
      "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."
    ],
    "help_url": "https://app.housekraft.co/api/v1/tools/walls/help"
  }
}
