{
  "help": {
    "id": "editor",
    "name": "AI Annotate",
    "what_it_does": "Mark part of the photo and say what to change there.",
    "about": "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.",
    "the_photo": "A room photo.",
    "credits": 3,
    "credits_label": "3 credits per run",
    "pricing": "3 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": "Run AI Annotate",
        "detail": "Answers straight away with the job, which keeps running.",
        "command": "curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/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\": \"make the pendant lamp brass\"\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": "prompt",
        "type": "text",
        "required": false,
        "description": "What to change, e.g. \"make this lamp brass\"."
      },
      {
        "name": "annotations",
        "type": "annotations",
        "required": false,
        "description": "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.",
        "max_items": 50
      },
      {
        "name": "ref_w",
        "type": "integer",
        "required": false,
        "description": "Optional. Width of the preview the marks were drawn on.",
        "min": 0,
        "max": 20000
      },
      {
        "name": "ref_h",
        "type": "integer",
        "required": false,
        "description": "Optional. Height of that preview.",
        "min": 0,
        "max": 20000
      }
    ],
    "required_one_of": [
      "prompt",
      "annotations"
    ],
    "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": "Describe the change in words",
      "method": "POST",
      "url": "https://app.housekraft.co/api/v1/tools/editor/run",
      "body": {
        "image": "<asset id from POST /api/v1/uploads>",
        "prompt": "make the pendant lamp brass"
      }
    },
    "example_curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/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\": \"make the pendant lamp brass\"\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 AI Annotate\njob = call(\"POST\", \"/api/v1/tools/editor/run\", json={\n    \"image\": photo,\n    \"prompt\": \"make the pendant lamp brass\",\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 AI Annotate\nlet { job } = await call(\"POST\", \"/api/v1/tools/editor/run\", {\n  \"image\": photo,\n  \"prompt\": \"make the pendant lamp brass\",\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": "Brush over the object, then say what to do",
        "body": {
          "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
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/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\": \"make this brass\",\n  \"annotations\": [\n    {\"type\": \"brush\", \"points\": [{\"x\": 410, \"y\": 120}, {\"x\": 450, \"y\": 160}], \"width\": 30}\n  ]\n}'"
      },
      {
        "title": "An arrow and a note, no prompt",
        "body": {
          "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
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/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  \"annotations\": [\n    {\"type\": \"arrow\", \"start\": {\"x\": 200, \"y\": 300}, \"end\": {\"x\": 320, \"y\": 260}},\n    {\"type\": \"text\", \"text\": \"remove this chair\", \"x\": 180, \"y\": 320}\n  ]\n}'"
      },
      {
        "title": "Marks drawn on a smaller preview of the photo",
        "body": {
          "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
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/editor/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\": \"replace with a round mirror\",\n  \"ref_w\": 800,\n  \"ref_h\": 600,\n  \"annotations\": [\n    {\"type\": \"brush\", \"points\": [{\"x\": 300, \"y\": 150}, {\"x\": 340, \"y\": 190}], \"width\": 25}\n  ]\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": "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\""
          }
        }
      },
      {
        "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": "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
          }
        }
      },
      {
        "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": "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."
            }
          }
        }
      }
    ],
    "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": 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": [
      "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."
    ],
    "help_url": "https://app.housekraft.co/api/v1/tools/editor/help"
  }
}
