{
  "help": {
    "id": "enmasse",
    "name": "En Masse",
    "what_it_does": "Try many wall colours, floors and themes on one room at once: one result for each.",
    "about": "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.",
    "the_photo": "A room photo, plus pictures of the floors or themes to try. Wall colours can be hex codes.",
    "credits": 1,
    "credits_label": "1 credit per reference",
    "pricing": "1 credit per reference (each comes back as one image); nothing if the run fails.",
    "output_kind": "images",
    "output": "One image for each photo (or each reference), saved to your Library. They are in job.outputs, each with its own status; each file is at outputs[].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 photos, one request each",
        "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 En Masse",
        "detail": "Answers straight away with the job, which keeps running.",
        "command": "curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/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  \"references\": [\n    {\"mode\": \"walls\", \"color\": \"#8A9A5B\"},\n    {\"mode\": \"walls\", \"color\": \"#F2EFE6\"},\n    {\"mode\": \"flooring\", \"image\": \"<asset id of a floor photo>\"}\n  ]\n}'"
      },
      {
        "title": "Get the results",
        "detail": "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.",
        "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": "references",
        "type": "references",
        "required": true,
        "description": "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.",
        "min_items": 2,
        "max_items": 52
      }
    ],
    "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": "Has no effect: the website has no image check for this tool."
    },
    "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": "Two wall colours and a floor",
      "method": "POST",
      "url": "https://app.housekraft.co/api/v1/tools/enmasse/run",
      "body": {
        "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>"
          }
        ]
      }
    },
    "example_curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/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  \"references\": [\n    {\"mode\": \"walls\", \"color\": \"#8A9A5B\"},\n    {\"mode\": \"walls\", \"color\": \"#F2EFE6\"},\n    {\"mode\": \"flooring\", \"image\": \"<asset id of a floor photo>\"}\n  ]\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 photos\nphoto = upload(\"room.jpg\")\nfloor = upload(\"oak-floor.jpg\")\n\n# 2. Run En Masse\njob = call(\"POST\", \"/api/v1/tools/enmasse/run\", json={\n    \"image\": photo,\n    \"references\": [\n      {\"mode\": \"walls\", \"color\": \"#8A9A5B\"},\n      {\"mode\": \"walls\", \"color\": \"#F2EFE6\"},\n      {\"mode\": \"flooring\", \"image\": floor}\n    ],\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    for item in job[\"outputs\"]:          # one per photo or reference, in order\n        print(item[\"index\"], item[\"status\"], item[\"output\"][\"url\"] if item[\"output\"] else item[\"error\"])\nelse:\n    print(\"Failed:\", job[\"error\"][\"message\"])\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 photos\nconst photo = await upload(\"room.jpg\");\nconst floor = await upload(\"oak-floor.jpg\");\n\n// 2. Run En Masse\nlet { job } = await call(\"POST\", \"/api/v1/tools/enmasse/run\", {\n  \"image\": photo,\n  \"references\": [\n    {\"mode\": \"walls\", \"color\": \"#8A9A5B\"},\n    {\"mode\": \"walls\", \"color\": \"#F2EFE6\"},\n    {\"mode\": \"flooring\", \"image\": floor}\n  ],\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\") {\n  // one per photo or reference, in order\n  for (const item of job.outputs) console.log(item.index, item.status, item.output ? item.output.url : item.error);\n} else {\n  console.log(\"Failed:\", job.error.message);\n}\n",
    "more_examples": [
      {
        "title": "Five colours of a paint brand, three floors of a tile brand, two themes of a category",
        "body": {
          "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
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/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  \"references\": [\n    {\"pick\": \"paints\", \"group\": \"Asian Paints\", \"count\": 5},\n    {\"pick\": \"flooring\", \"group\": \"Kajaria\", \"count\": 3},\n    {\"pick\": \"themes\", \"group\": \"Japandi\", \"count\": 2}\n  ]\n}'"
      },
      {
        "title": "Ten real green paint shades, picked at random",
        "body": {
          "image": "<asset id from POST /api/v1/uploads>",
          "references": [
            {
              "pick": "paints",
              "search": "green",
              "count": 10,
              "random": true
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/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  \"references\": [{\"pick\": \"paints\", \"search\": \"green\", \"count\": 10, \"random\": true}]\n}'"
      },
      {
        "title": "Compare three themes",
        "body": {
          "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>"
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/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  \"references\": [\n    {\"mode\": \"themes\", \"image\": \"<asset id of theme 1>\"},\n    {\"mode\": \"themes\", \"image\": \"<asset id of theme 2>\"},\n    {\"mode\": \"themes\", \"image\": \"<asset id of theme 3>\"}\n  ]\n}'"
      },
      {
        "title": "Walls, floors and a theme in one run",
        "body": {
          "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>"
            }
          ]
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/enmasse/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  \"references\": [\n    {\"mode\": \"walls\", \"color\": \"#1F3A5F\"},\n    {\"mode\": \"flooring\", \"image\": \"<asset id of a tile photo>\"},\n    {\"mode\": \"themes\", \"image\": \"<asset id of a theme photo>\"}\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": "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\""
          }
        }
      },
      {
        "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": "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"
          }
        }
      },
      {
        "title": "Failed",
        "status": 200,
        "when": "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.",
        "body": {
          "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": []
          }
        }
      }
    ],
    "checks": [
      "Image check: the website has no photo check for this tool, so only the misuse check runs.",
      "Misuse check: every photo must show a room, building or plan (references may also be materials or swatches), 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": [
      "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."
    ],
    "help_url": "https://app.housekraft.co/api/v1/tools/enmasse/help"
  }
}
