{
  "help": {
    "id": "walkthrough_multi",
    "name": "Walkthrough Video (multiple photos)",
    "what_it_does": "One continuous walkthrough across several photos of a property.",
    "about": "Makes one continuous video that moves through several photos in order: different rooms of a home, or several views of one room.",
    "the_photo": "1 to 7 photos, in the order the camera should visit them.",
    "credits": 2,
    "credits_label": "2 credits for 6 s and 1 photo",
    "pricing": "Priced by video length and number of photos; see GET /api/v1/tools/pricing. Refunded if the run fails.",
    "output_kind": "video",
    "output": "One MP4 video, 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 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=@front-of-house.jpg"
      },
      {
        "title": "Run Walkthrough Video (multiple photos)",
        "detail": "Answers straight away with the job, which keeps running.",
        "command": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"images\": [\"<front of house>\", \"<hallway>\", \"<living room>\"],\n  \"duration\": 8,\n  \"scope\": \"property\"\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": "images",
        "type": "images",
        "required": true,
        "description": "1–7 photos, in the order the camera should visit them.",
        "help": "An asset id (from POST /api/v1/uploads or your Library) or an https:// image URL.",
        "min_items": 1,
        "max_items": 7
      },
      {
        "name": "scope",
        "type": "choice",
        "required": false,
        "description": "\"property\" for different rooms of one home, \"room\" for several views of one room.",
        "options": [
          "property",
          "room"
        ],
        "free_text": false,
        "default": "property"
      },
      {
        "name": "duration",
        "type": "integer",
        "required": false,
        "description": "Video length in seconds.",
        "min": 4,
        "max": 15,
        "default": 6
      }
    ],
    "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": "A walkthrough of a home from three photos",
      "method": "POST",
      "url": "https://app.housekraft.co/api/v1/tools/walkthrough_multi/run",
      "body": {
        "images": [
          "<front of house>",
          "<hallway>",
          "<living room>"
        ],
        "duration": 8,
        "scope": "property"
      }
    },
    "example_curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"images\": [\"<front of house>\", \"<hallway>\", \"<living room>\"],\n  \"duration\": 8,\n  \"scope\": \"property\"\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\nphotos = [upload(path) for path in [\"front-of-house.jpg\", \"hallway.jpg\", \"living-room.jpg\"]]\n\n# 2. Run Walkthrough Video (multiple photos)\njob = call(\"POST\", \"/api/v1/tools/walkthrough_multi/run\", json={\n    \"images\": photos,\n    \"duration\": 8,\n    \"scope\": \"property\",\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 photos\nconst photos = await Promise.all([\"front-of-house.jpg\", \"hallway.jpg\", \"living-room.jpg\"].map(upload));\n\n// 2. Run Walkthrough Video (multiple photos)\nlet { job } = await call(\"POST\", \"/api/v1/tools/walkthrough_multi/run\", {\n  \"images\": photos,\n  \"duration\": 8,\n  \"scope\": \"property\",\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": "Several views of one room",
        "body": {
          "images": [
            "<view 1>",
            "<view 2>"
          ],
          "scope": "room",
          "duration": 6
        },
        "curl": "curl -s -X POST https://app.housekraft.co/api/v1/tools/walkthrough_multi/run \\\n  -H \"Authorization: Bearer $HOUSEKRAFT_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"images\": [\"<view 1>\", \"<view 2>\"], \"scope\": \"room\", \"duration\": 6}'"
      }
    ],
    "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": "walkthrough_multi",
            "status": "running",
            "created_at": 1793700000,
            "finished_at": null,
            "credits": {
              "charged": 2,
              "refunded": false
            },
            "input": {
              "images": [
                "<front of house>",
                "<hallway>",
                "<living room>"
              ],
              "duration": 8,
              "scope": "property"
            },
            "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": "walkthrough_multi",
            "status": "done",
            "created_at": 1793700000,
            "finished_at": 1793700025,
            "credits": {
              "charged": 2,
              "refunded": false
            },
            "input": {
              "images": [
                "<front of house>",
                "<hallway>",
                "<living room>"
              ],
              "duration": 8,
              "scope": "property"
            },
            "warnings": [],
            "check_overridden": false,
            "output": {
              "id": "a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5",
              "type": "generated",
              "tool": "walkthrough",
              "url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
              "thumbnail_url": "https://…/generated/a3f0c6d1-8b27-4c5e-9f14-6e2b7d90c3b5.mp4",
              "width": null,
              "height": null,
              "created_at": 1793700025,
              "kind": "video"
            },
            "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": "walkthrough_multi",
            "status": "failed",
            "created_at": 1793700000,
            "finished_at": 1793700025,
            "credits": {
              "charged": 2,
              "refunded": true
            },
            "input": {
              "images": [
                "<front of house>",
                "<hallway>",
                "<living room>"
              ],
              "duration": 8,
              "scope": "property"
            },
            "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": [
      "List the photos in the order the camera should visit them.",
      "The price depends on the length and the number of photos: see GET /api/v1/tools/pricing."
    ],
    "help_url": "https://app.housekraft.co/api/v1/tools/walkthrough_multi/help"
  }
}
