{"openapi":"3.1.0","info":{"title":"Space Context API","version":"v1"},"servers":[{"url":"https://api.spacecontext.app/v1"}],"paths":{"/":{"get":{"operationId":"getApiInfo","security":[],"summary":"API metadata","description":"Where the contract, the docs and the MCP server are, and the input limits of this deployment (`limits`).","responses":{"200":{"description":"API metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiInfo"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApiDocument","security":[],"summary":"OpenAPI document","responses":{"200":{"description":"OpenAPI document"}}}},"/me":{"get":{"operationId":"getCurrentKey","summary":"Get the current API key user","security":[{"apiKey":[]}],"responses":{"200":{"description":"Current API key user","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/media/{key}":{"get":{"operationId":"getEvidenceFrame","security":[],"summary":"Serve a signed v1 evidence frame","parameters":[{"name":"key","in":"path","required":true,"description":"Storage key of the frame, exactly as in the signed URL: `frames/…` for spaces scanned with the app, `api/…` for scans made through the API.","schema":{"type":"string","pattern":"^(frames|api)/"}},{"name":"token","in":"query","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Original evidence frame bytes","headers":{"Cache-Control":{"schema":{"type":"string"}},"X-Content-Type-Options":{"schema":{"type":"string"}}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/heic":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces":{"get":{"operationId":"listSpaces","summary":"List the spaces this key can read: scanned with the app (live keys) and created through the API","security":[{"apiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","required":false,"description":"Only spaces from this source. Omitted: every space this key can read (app spaces need a live key; test keys see the demo space).","schema":{"type":"string","enum":["app","api"]}}],"responses":{"200":{"description":"Spaces page","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceListResponse"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createSpace","summary":"Create a space to scan through the API","description":"Creates a space in the key's mode (test or live). With an `external_id` already used in that mode, returns the existing space with 200 instead of creating another. An account creates at most 200 spaces a day in each mode (by default, test keys included; a replay does not count): past that, 429 `RATE_LIMIT_EXCEEDED` with `Retry-After` (seconds to midnight UTC). Needs `spaces:read` and `scans:write`.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceCreateRequest"}}}},"responses":{"200":{"description":"The space with this external_id","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSpace"}}}},"201":{"description":"Space created","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSpace"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces/{id}":{"get":{"operationId":"getSpace","summary":"Get the current document for a space","description":"Keys without the `spaces:sensitive` scope only read what the deep pass has screened for sensitive values. Until it has (`enrichment.level` is `base`), the document has the same shape with its content withheld: the lists are empty and `stats` gives the counts. Once `enrichment.level` is `deep`, sensitive facts are redacted, their values are removed from every other text, and images are given only for the frames that non-sensitive records cite as evidence. Fields the deep pass does not read (project description, summary, area layout) and raw first-hand sources (transcript, owner answers, narrator quotes) always require the scope.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"include","in":"query","required":false,"description":"Adds the transcript. Requires the `spaces:sensitive` scope; without it the request is refused with 403 `INSUFFICIENT_SCOPE`.","schema":{"type":"string","enum":["transcript"]}},{"name":"enrich","in":"query","required":false,"schema":{"type":"string","enum":["auto","none"],"default":"auto"}}],"responses":{"200":{"description":"Space document","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceDocument"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"deleteSpace","summary":"Delete a space created through the API, with its scans and files","description":"Not possible while one of its scans is processing (409). App spaces are deleted in the app. Needs `scans:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}}],"responses":{"204":{"description":"Deleted"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces/{id}/scans":{"get":{"operationId":"listSpaceScans","summary":"List scans for a space","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}}],"responses":{"200":{"description":"Scan summaries","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanListResponse"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createScan","summary":"Create a scan of a space created through the API","description":"Declares what will be uploaded (frames with their timestamps, or one video), the language of the report (`en`, `it`, `es`, `fr`, `de`, `pt`, `nl`; the narration can be in any language) and the optional transcript. The response says how to upload in `upload`; then call `POST /scans/{id}/start`. A live scan needs a card on file, asked here, before any upload link: 402 `PAYMENT_METHOD_REQUIRED` (added in the console, nothing charged), 503 `PAYMENTS_UNAVAILABLE` where payments are not configured; the same rule as the start. A test scan never needs one; its uploads go through the API (a video of at most 95 MiB) and are counted, never stored. The same `Idempotency-Key` returns the same scan. An account creates at most 500 scans a day in each mode (by default, test keys included; a replay does not count): past that, 429 `RATE_LIMIT_EXCEEDED` with `Retry-After` (seconds to midnight UTC). An account holds at most 20 scans created and not started (both modes): past that, 429 `SCANS_IN_PROGRESS` (`max_open_scans`) until some are started or cancelled. A test scan keeps only whether it had a transcript, never its text (its result is the demo). Needs `scans:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanCreateRequest"}}}},"responses":{"200":{"description":"The scan created with this Idempotency-Key","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"201":{"description":"Scan created","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"402":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ErrorResponse"}}}},"/spaces/{id}/scans/{scan_id}":{"get":{"operationId":"getSpaceScan","summary":"Get a base document for a specific scan","description":"A scan read by id is the snapshot as it was recorded. Past scans are not screened for sensitive values, so this operation requires the `spaces:sensitive` scope and answers 403 `INSUFFICIENT_SCOPE` without it.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_"}},{"name":"include","in":"query","required":false,"description":"Adds the transcript. Requires the `spaces:sensitive` scope; without it the request is refused with 403 `INSUFFICIENT_SCOPE`.","schema":{"type":"string","enum":["transcript"]}}],"responses":{"200":{"description":"Space document for the scan","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceDocument"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces/{id}/skill":{"get":{"operationId":"getSpaceSkill","summary":"Export a space as a multi-file Agent Skill","description":"Returns the files of a ready-to-use skill built from the current document, exactly as this key may read it: a redacted document gives a redacted skill. `SKILL.md` is a short static guide; the knowledge is in `references/` and `space.json`, which an agent opens only when needed. A key without `spaces:sensitive` gets 409 `ENRICHMENT_PENDING` until the deep pass has screened the analysis.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"include","in":"query","required":false,"description":"Adds `references/transcript.md`. Requires the `spaces:sensitive` scope; without it the request is refused with 403 `INSUFFICIENT_SCOPE`.","schema":{"type":"string","enum":["transcript"]}},{"name":"enrich","in":"query","required":false,"schema":{"type":"string","enum":["auto","none"],"default":"auto"}}],"responses":{"200":{"description":"Skill bundle","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkillBundle"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces/{id}/markdown":{"get":{"operationId":"getSpaceMarkdown","summary":"Export a space as one Markdown page","description":"The current document as a single readable page (`text/markdown`): summary, every area with its items, issues, key locations and how-to steps, then facts, Q&A, coverage and the recording. Built from exactly the document this key may read, so a redacted document gives a redacted page; image links are never included. The same 409 `ENRICHMENT_PENDING` rule as the Skill export applies to keys without `spaces:sensitive`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"include","in":"query","required":false,"description":"Adds the transcript. Requires the `spaces:sensitive` scope; without it the request is refused with 403 `INSUFFICIENT_SCOPE`.","schema":{"type":"string","enum":["transcript"]}},{"name":"enrich","in":"query","required":false,"schema":{"type":"string","enum":["auto","none"],"default":"auto"}}],"responses":{"200":{"description":"Markdown page","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces/{id}/ask":{"post":{"operationId":"askSpace","summary":"Ask a question about a space","description":"Answers from the records of the current document this key may read, in one to three sentences, with the records it relies on and their evidence images. Says so when the answer is not documented, and never reveals a redacted value. Test keys get the best-matching record of the demo, without a model. A key without `spaces:sensitive` gets 409 `ENRICHMENT_PENDING` until the space is screened. Limits: 20 a minute and, by default, 200 a day per account.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string","minLength":1,"maxLength":500}}}}}},"responses":{"200":{"description":"Answer","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceAnswer"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/spaces/{id}/diff":{"get":{"operationId":"getSpaceDiff","summary":"Compare two scans of a space","description":"What changed from the scan `from` to the scan `to` (the current one when omitted): areas, items added, removed, not seen, moved or changed, issues new, resolved or not seen. Computed without a model. When `to` was checked against `from` (consecutive scans of a space created through the API) records are matched by the links it made, and removals are confirmed by a frame of the empty place; otherwise records are matched by content (name, generic name, aliases) and what is missing is reported as `not_seen`. For a space scanned with the app, past scans are not screened for sensitive values, so this operation requires the `spaces:sensitive` scope.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"from","in":"query","required":true,"description":"Id of the earlier scan.","schema":{"type":"string","pattern":"^scn_"}},{"name":"to","in":"query","required":false,"description":"Id of the later scan. Defaults to the current one.","schema":{"type":"string","pattern":"^scn_"}}],"responses":{"200":{"description":"Differences between the two scans","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceDiff"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/search":{"get":{"operationId":"searchSpaces","summary":"Search across all your spaces","description":"Finds the records (items, areas, issues, key locations, instructions, facts, questions and answers) that best match a question in natural language, across every space this key can read, in any of 100+ languages: \"where did I see a ladder?\". Each result names its space, scan and record, so it can be opened with the other operations. The index holds only screened, redacted content: no result ever contains a sensitive value, whatever the key's scopes. A space appears once its current scan has been indexed: after a scan completes, or after the deep pass of a space scanned with the app. Test keys search the demo space, without a model. Limits: 20 a minute and, by default, 1000 a day per account.","security":[{"apiKey":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":300}},{"name":"space_id","in":"query","required":false,"description":"Searches one space only.","schema":{"type":"string","pattern":"^(app|spc)_"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":25,"default":10}}],"responses":{"200":{"description":"Best matches first","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResults"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ErrorResponse"}}}},"/wallet":{"get":{"operationId":"getWallet","summary":"What this key has to spend","description":"For agents: the balance, the free scans left, the price of a scan and whether a card is on file (`card_on_file`: live scans need one). A test key reads a fixed test-mode wallet instead (balance 0, no free scan, `card_on_file` false, automatic recharge off, the live price): test scans cost nothing and need no card. Needs the `scans:read` scope.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Wallet","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Wallet"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/scans/{id}":{"get":{"operationId":"getScan","summary":"Status of a scan","description":"Poll until `status` is `complete` or `failed`. A failed scan costs nothing: it is charged at start and refunded on failure, and `charge.refunded` says so. Needs `scans:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"}}],"responses":{"200":{"description":"Scan","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"cancelScan","summary":"Cancel a scan that has not started","description":"Nothing was charged; its uploads are deleted. Needs `scans:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"}}],"responses":{"204":{"description":"Cancelled"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/scans/{id}/events":{"get":{"operationId":"streamScanEvents","summary":"Follow a scan in real time","description":"A `text/event-stream` (Server-Sent Events) that sends the scan (the same object as `GET /scans/{id}`, without `upload`) as an event named `scan` at once, then every time its status, stage or error changes, and closes after the final one (`complete`, `failed` or `cancelled`). A `ping` event every 15 seconds keeps the connection alive. One stream lasts at most 30 minutes; reconnect to keep following. An account holds at most 10 streams open at once (by default): one more answers 429 with `Retry-After`; close one, or poll `GET /scans/{id}`. Needs `scans:read`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"}}],"responses":{"200":{"description":"Event stream: `event: scan` with the scan as JSON data; `event: ping` with no data.","headers":{"Cache-Control":{"schema":{"type":"string"}},"Content-Type":{"schema":{"type":"string","enum":["text/event-stream"]}}},"content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/scans/{id}/frames/{index}":{"put":{"operationId":"uploadScanFrame","summary":"Upload one frame of a frames scan","description":"The raw image as the body: JPEG, PNG or WebP (recognised by its bytes), at most 2 MB. Sending a frame again replaces it. A test scan checks the frame the same way and only records that it arrived: nothing of a test scan is stored (its result is the demo). Needs `scans:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"}},{"name":"index","in":"path","required":true,"schema":{"type":"integer","minimum":0,"maximum":119}}],"requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Frame stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanUpload"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"413":{"$ref":"#/components/responses/ErrorResponse"},"415":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/scans/{id}/video":{"put":{"operationId":"uploadScanVideo","summary":"Upload the video of a video scan through the API","description":"Only when `upload.url` points here (no direct upload link available; always for a test scan, whose video is counted and dropped, never stored). `Content-Length` and `Content-Type` must match what was declared, and the body must be that size. At most 99614720 bytes (95 MiB): a bigger video answers 413 `FILE_TOO_LARGE` and goes to the direct upload link instead. Needs `scans:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"}}],"requestBody":{"required":true,"content":{"video/mp4":{"schema":{"type":"string","format":"binary"}},"video/quicktime":{"schema":{"type":"string","format":"binary"}},"video/x-m4v":{"schema":{"type":"string","format":"binary"}},"video/3gpp":{"schema":{"type":"string","format":"binary"}},"video/webm":{"schema":{"type":"string","format":"binary"}},"video/x-matroska":{"schema":{"type":"string","format":"binary"}},"video/matroska":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Video stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanUpload"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"413":{"$ref":"#/components/responses/ErrorResponse"},"415":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/scans/{id}/start":{"post":{"operationId":"startScan","summary":"Charge the scan and start the analysis","description":"Checks that every frame (or the video) is uploaded, uses the free scan or charges the wallet, and starts the analysis. Answers 202 when it started the scan and 200 when the scan had already started. A failed analysis is refunded. Errors: 402 `PAYMENT_METHOD_REQUIRED` (a live scan needs a card on file: added in the console, nothing charged), `INSUFFICIENT_FUNDS` or `SPENDING_CAP_REACHED`, 409 `UPLOAD_INCOMPLETE`, 429 `SCANS_IN_PROGRESS`, 429 `RATE_LIMIT_EXCEEDED` with `Retry-After` when 5 live scans of the account failed that day because of their footage (no usable frame, an unreadable or too long video, nothing recognisable: each refunded) until midnight UTC, 503 `PAYMENTS_UNAVAILABLE` (a live scan where payments are not configured). Test keys are never charged and get the demo result. Needs `scans:write`.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"}}],"responses":{"200":{"description":"Already started","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"202":{"description":"Started","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"401":{"$ref":"#/components/responses/ErrorResponse"},"402":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"},"409":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ErrorResponse"}}}},"/frames/check":{"post":{"operationId":"checkFrame","summary":"Check one frame while filming","description":"For apps that capture a space and guide the person filming: is this frame usable, which key locations does it show and, with `space_type`, which area. With `space_type` the key locations are those of that type of space (`GET /capture-profiles`: a car is checked for its VIN plate and dashboard, a boat for its seacocks), and `area` says where the frame was taken; without it, a home's: electrical_panel, water_shutoff, water_heater, smoke_detector, fire_extinguisher, router, heating_control, gas_meter, and no area. A key location is `seen` from a probability of 0.7. The raw image as the body: JPEG, PNG or WebP, at most 2 MB. About half a second; nothing is stored. Free within a daily allowance per account (429 when used up). Test keys get a fixed answer. Needs `scans:write`; a live check needs a card on file, like a live scan. Errors: 400 `VALIDATION_ERROR` (unknown `space_type`), 402 `PAYMENT_METHOD_REQUIRED` (live, no card on file: added in the console, nothing charged), 415 `INVALID_CONTENT_TYPE`, 503 `FRAME_CHECK_UNAVAILABLE`, 503 `PAYMENTS_UNAVAILABLE` (live, where payments are not configured).","security":[{"apiKey":[]}],"parameters":[{"name":"space_type","in":"query","required":false,"description":"The type of space being filmed: its key locations, and the area question.","schema":{"type":"string","enum":["apartment","house","office","warehouse","vehicle","boat","camper","shop","garage","appliance","other"]}}],"requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"What the frame shows","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FrameCheck"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"401":{"$ref":"#/components/responses/ErrorResponse"},"402":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"413":{"$ref":"#/components/responses/ErrorResponse"},"415":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ErrorResponse"}}}},"/capture-profiles":{"get":{"operationId":"listCaptureProfiles","security":[],"summary":"What to film, for every type of space","description":"For each type of space, its key locations (what people and agents ask about first: the fuse box of a flat, the VIN plate of a car, the seacocks of a boat), each with a one-line hint of what to film and whether it is usually there (`expected`), and its areas. The same for everyone: no key, cacheable. Use it to show a checklist while filming, with `POST /frames/check?space_type=`.","parameters":[{"name":"language","in":"query","required":false,"description":"The language of the labels and hints: `en`, `it`, `es`, `fr`, `de`, `pt`, `nl` (default `en`).","schema":{"type":"string","enum":["en","it","es","fr","de","pt","nl"],"default":"en"}}],"responses":{"200":{"description":"Every capture profile","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureProfileList"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"}}}},"/capture-profiles/{space_type}":{"get":{"operationId":"getCaptureProfile","security":[],"summary":"What to film, for one type of space","description":"One type of space: its key locations with a hint each, and its areas. Errors: 400 `VALIDATION_ERROR` (unknown language), 404 `NOT_FOUND` (unknown type).","parameters":[{"name":"space_type","in":"path","required":true,"schema":{"type":"string","enum":["apartment","house","office","warehouse","vehicle","boat","camper","shop","garage","appliance","other"]}},{"name":"language","in":"query","required":false,"description":"The language of the labels and hints: `en`, `it`, `es`, `fr`, `de`, `pt`, `nl` (default `en`).","schema":{"type":"string","enum":["en","it","es","fr","de","pt","nl"],"default":"en"}}],"responses":{"200":{"description":"The capture profile","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureProfile"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"404":{"$ref":"#/components/responses/ErrorResponse"}}}},"/auth/device":{"post":{"operationId":"startDeviceSignIn","summary":"Start a device sign-in (the CLI)","description":"A terminal that has no browser session asks for a code. It shows `user_code` and `verification_url` to the person, who opens the console and types the code (never a link with the code in it: a link could be sent by someone else to get their terminal approved), then polls `POST /auth/device/token` with `device_code` every `interval` seconds. The code lives `expires_in` seconds. Public; limited per IP.","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceStartRequest"}}}},"responses":{"201":{"description":"Codes for the terminal and the person","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceStart"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/auth/device/token":{"post":{"operationId":"claimDeviceSignIn","summary":"Poll a device sign-in","description":"Answers 400 `AUTHORIZATION_PENDING` until the person decides, 400 `SLOW_DOWN` when polled faster than `interval`, 400 `DEVICE_CODE_EXPIRED` after ten minutes, 403 `ACCESS_DENIED` when denied, and the new API key exactly once when approved. Public.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenRequest"}}}},"responses":{"200":{"description":"The key, once","headers":{"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceToken"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"403":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/agent/credits":{"post":{"operationId":"buyAgentCredits","summary":"Pay with x402 and get an API key (agents)","description":"For an agent with a wallet and no account: pay in USDC on **Base** or **Solana** with [x402](https://x402.org) v2 and receive an API key. No key, no sign-in: the payment is the credential, and the paying address is the account. Any x402 v2 client works (for example `@x402/fetch` with `@x402/evm` and `@x402/svm`): the network fee is paid by the facilitator, the payer needs only USDC.\n\n1. Call without `PAYMENT-SIGNATURE`: 402 `PAYMENT_REQUIRED` with the `PAYMENT-REQUIRED` header (base64 JSON, also in the body as `payment_required`). `accepts` has one entry per network, Base first: scheme `exact`, the network (CAIP-2), the amount in atomic USDC units (6 decimals: US$1.00 = `1000000`), the USDC contract or mint, the address to pay, and `extra` (the EIP-712 domain of USDC on Base; on Solana `feePayer`, the facilitator account that pays the network fee). `extensions.bazaar` describes the call for x402 catalogs.\n2. Pick one entry, sign it, and send **the same request, with the same body**, adding `PAYMENT-SIGNATURE` (base64 JSON PaymentPayload with that entry, unchanged, as `accepted`). On Base the payload is an EIP-3009 `transferWithAuthorization`; on Solana a transaction partially signed by the payer: SetComputeUnitLimit, SetComputeUnitPrice, a `TransferChecked` of exactly the amount into the USDC account of `payTo`, optional memo, fee payer `extra.feePayer`. The payment is verified and settled by the facilitator; the answer carries `PAYMENT-RESPONSE` with the transaction.\n\nOn mainnet (Base `eip155:8453`, Solana `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) the payment is credited to the account's wallet (`amount_cents`, then US$1.00 per live scan; reading, search and answers are included; agents have no free scan; the credit does not expire) and the key is **live**. On testnets (Base Sepolia `eip155:84532`, Solana devnet `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) USDC is free, so the payment credits nothing: the key is a **test** key (demo space, no charges, no model). The same paying address always gets the same account; a Base wallet and a Solana wallet are two accounts.\n\n**Store `api_key` from your first purchase: it is shown once.** A signed authorization can be submitted by whoever holds it, so a payment alone returns a key only when it opens the account. Later payments from the same wallet top up the same account and return only `api_key_prefix`. An existing key is replaced only with that key: `rotate_key: true` plus `Authorization: Bearer <the current key>` (the previous key is revoked). The first key of the other mode (e.g. a live key for an account that so far paid on testnet) needs `Authorization: Bearer <one of the account's current keys>`. Both are checked before any payment is taken (403 `AGENT_KEY_REQUIRED`, nothing charged). A payment replayed with the same transaction is recognised: nothing is credited or minted twice.\n\n400 `PAYMENT_INVALID` when the signature header is malformed or does not match the requirements (amount, network, asset, address, fee payer, the payer's signature), before anything is sent to the facilitator. 402 `PAYMENT_FAILED` when the facilitator rejects or cannot settle the payment (`reason`; nothing was charged). 503 `PAYMENTS_UNAVAILABLE` when the chosen network cannot be served right now (nothing was charged). 404 when agent payments are off on this deployment. Limited per IP.","security":[{},{"apiKey":[]}],"parameters":[{"name":"PAYMENT-SIGNATURE","in":"header","required":false,"description":"base64 of the JSON x402 v2 PaymentPayload: `x402Version: 2`, `accepted` (one entry of `accepts`, unchanged), and `payload`: on Base `{ signature, authorization: { from, to, value, validAfter, validBefore, nonce } }`, on Solana `{ transaction }` (base64 of the partially signed transaction).","schema":{"type":"string","maxLength":16384}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCreditRequest"}}}},"responses":{"200":{"description":"Paid: the account, its credit (mainnet) and its key","headers":{"PAYMENT-RESPONSE":{"description":"base64 of the JSON settlement: `{ success, transaction, network, payer }`","schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCredit"}}}},"400":{"description":"Invalid body, or `PAYMENT_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPaymentError"}}}},"402":{"description":"`PAYMENT_REQUIRED` (pay first) or `PAYMENT_FAILED` (rejected or not settled; nothing was charged)","headers":{"PAYMENT-REQUIRED":{"description":"base64 of the JSON PaymentRequired","schema":{"type":"string"}},"PAYMENT-RESPONSE":{"description":"On `PAYMENT_FAILED`: base64 of `{ success: false, errorReason, payer, transaction, network }`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPaymentError"}}}},"403":{"description":"`AGENT_KEY_REQUIRED`: changing the keys of an existing account needs one of its current keys (nothing was charged)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPaymentError"}}}},"404":{"$ref":"#/components/responses/ErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Settled but not recorded: reconciled by hand (`transaction`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPaymentError"}}}},"502":{"description":"The settlement could not be confirmed (`nonce` on Base, `signature` on Solana)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPaymentError"}}}},"503":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"sc_live or sc_test"}},"responses":{"RateLimited":{"description":"Too many requests. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ErrorResponse":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"ApiInfo":{"type":"object","required":["name","version","openapi","limits"],"properties":{"name":{"type":"string","const":"space-context-api"},"version":{"type":"string","const":"v1"},"openapi":{"type":"string"},"docs":{"type":"string","format":"uri"},"llms":{"type":"string","format":"uri"},"llms_full":{"type":"string","format":"uri"},"mcp":{"type":"string","format":"uri"},"console":{"type":"string","format":"uri"},"limits":{"type":"object","description":"What one scan may send to this deployment.","required":["max_video_bytes","max_video_seconds","max_photos"],"properties":{"max_video_bytes":{"type":"integer","minimum":1,"maximum":4294967296,"description":"The largest video a scan may declare (`input.video.content_length`): 4294967296 (4 GiB) where the API reads videos with its own ffmpeg ingest, 104857600 (100 MiB) otherwise, and never more than 99614720 (95 MiB) where no direct upload link is issued."},"max_video_seconds":{"type":"integer","description":"The longest video read; a longer one fails with `VIDEO_TOO_LONG`, not charged."},"max_photos":{"type":"integer","description":"The most frames (photos) one scan declares (`input.frames`)."}}}}},"ConsoleUser":{"type":"object","required":["id","email","name","is_pro","is_admin"],"properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":["string","null"]},"is_pro":{"type":"boolean"},"is_admin":{"type":"boolean","description":"True only for Space Context staff accounts, which the console shows extra internal pages; false for everyone else."}}},"ApiKeyScope":{"type":"string","enum":["spaces:read","scans:read","scans:write","spaces:sensitive"]},"MeResponse":{"type":"object","required":["user","key"],"properties":{"user":{"type":"object","required":["id","email","is_pro"],"properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"is_pro":{"type":"boolean"}}},"key":{"type":"object","required":["id","mode","scopes"],"properties":{"id":{"type":["string","null"],"pattern":"^key_","description":"Null when the caller is the developer console, signed in as the owner."},"mode":{"type":"string","enum":["live","test"]},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyScope"}}}}}},"ConditionCode":{"type":"string","enum":["new","excellent","good","fair","poor","damaged","not_working","unknown"]},"AreaType":{"type":"string","enum":["entrance","hallway","living_room","kitchen","dining_room","bedroom","bathroom","laundry","office","storage","garage","balcony_terrace","garden_exterior","stairs","technical_room","sales_floor","warehouse_floor","vehicle_cabin","vehicle_exterior","engine_bay","boat_deck","boat_cabin","appliance_view","other"]},"ItemCategory":{"type":"string","enum":["furniture","appliance","electronics","lighting","fixture","kitchenware","textile","decor","storage","tool","safety","utility","climate","plumbing","document","consumable","vehicle_part","structural","other"]},"IssueCategory":{"type":"string","enum":["damage","wear","malfunction","missing","cleanliness","safety","maintenance","other"]},"IssueSeverity":{"type":"string","enum":["low","medium","high","critical"]},"KeyLocationKind":{"type":"string","enum":["electrical_panel","water_shutoff","gas_shutoff","water_heater","heating_control","air_conditioning_control","router","fire_extinguisher","smoke_detector","first_aid","emergency_exit","keys","waste_recycling","electricity_meter","gas_meter","water_meter","alarm_panel","spare_supplies","identification_plate","instrument_panel","engine_bay","spare_wheel","safety_equipment","battery_isolator","bilge_pump","seacock","fuel_shutoff","water_tank","sprinkler_valve","control_panel","filter","utility_connections","other"]},"FactKind":{"type":"string","enum":["access","wifi","rule","contact","schedule","quirk","measurement","history","other"]},"PlacementRelation":{"type":"string","enum":["on","inside","under","above","below","left_of","right_of","in_front_of","behind","opposite","beside","unknown"],"description":"How a thing sits against its anchor. `left_of`, `right_of`, `in_front_of`, `behind` and `opposite` hold only from the placement's `viewpoint`; the others hold from anywhere. `unknown`: the recording does not show where it is."},"SpacePlacement":{"type":"object","description":"Where a thing is, as anchor + relation + viewpoint, so that a person or an agent can orient themselves (\"on the wall left of the entrance door, facing the door from inside\"). `location` on the same record is this placement written as one sentence in the report language.","required":["anchor","anchor_item_id","relation","viewpoint","detail"],"properties":{"anchor":{"type":["string","null"],"description":"A fixed landmark the thing is placed against: a door, a window, the hob, a radiator. Never the thing itself."},"anchor_item_id":{"type":["string","null"],"description":"The id of the item in this document that is the anchor, when the anchor is one of its items."},"relation":{"$ref":"#/components/schemas/PlacementRelation"},"viewpoint":{"type":["string","null"],"description":"Where a person stands and what they face for the relation to hold (\"inside the flat, facing the entrance door\"). Always set for a lateral relation. One canonical viewpoint per area: facing the entrance door from inside for an entrance or hallway, standing in the doorway one enters it from and looking in for any other area. An agent describing the place to someone mirrors left and right only when it knows that person's own viewpoint; otherwise it repeats this one."},"detail":{"type":["string","null"],"description":"Height, the part of the anchor, or a second anchor: \"at chest height, above the shoe rack\"."}}},"SpaceType":{"type":["string","null"],"enum":["apartment","house","office","warehouse","vehicle","boat","camper","shop","garage","appliance","other",null]},"SpaceEvidenceRef":{"type":"object","required":["frame_id","timestamp_ms"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]}}},"SpaceVerification":{"type":"object","description":"The record checked again on the frames it cites, by a second model that answers closed questions with probabilities and never writes text. `verified`: one of its frames clearly shows it (probability 0.7 or more); `unconfirmed`: its frames were checked and none clearly does, so treat it as uncertain; `not_visual`: nothing a photo could confirm (it comes from the narration, or states a fault or something missing).","required":["status","probability","frame_id"],"properties":{"status":{"type":"string","enum":["verified","unconfirmed","not_visual"]},"probability":{"type":["number","null"],"minimum":0,"maximum":1,"description":"Best probability over the frames checked. Null for `not_visual`."},"frame_id":{"type":["string","null"],"description":"The frame that shows it best; it is also the first of the record's `evidence`."}}},"SpaceFrame":{"type":"object","required":["id","index","timestamp_ms","url","url_expires_at"],"properties":{"id":{"type":"string"},"index":{"type":"integer"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"format":"uri"},"url_expires_at":{"type":["string","null"],"format":"date-time"}}},"SpaceCondition":{"type":"object","required":["code","label","notes"],"properties":{"code":{"$ref":"#/components/schemas/ConditionCode"},"label":{"type":["string","null"],"description":"The label as written in the language of the report (\"Good\", \"Buono\", \"Bueno\", \"Gut\"...); `code` is the same in every language."},"notes":{"type":["string","null"]}}},"SpaceArea":{"type":"object","required":["id","name","type","level","condition","description","layout","orientation","features","access_from","item_ids","issue_ids","evidence","confidence"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"anyOf":[{"$ref":"#/components/schemas/AreaType"},{"type":"null"}]},"level":{"type":["string","null"]},"condition":{"$ref":"#/components/schemas/SpaceCondition"},"description":{"type":["string","null"]},"layout":{"type":["string","null"]},"orientation":{"type":["string","null"],"description":"One sentence that places the main fixed things of the area from its canonical viewpoint (facing the entrance door from inside for an entrance or hallway; standing in the doorway one enters it from, looking in, for any other area): \"Coming in from the hallway: window ahead, bed on the left, desk on the right.\" Null when the recording does not show enough, and always null at the base level."},"features":{"type":"array","items":{"type":"string"}},"access_from":{"type":"array","items":{"type":"string"}},"item_ids":{"type":"array","items":{"type":"string"}},"issue_ids":{"type":"array","items":{"type":"string"}},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}},"confidence":{"type":["number","null"],"minimum":0,"maximum":1}}},"SpaceItem":{"type":"object","required":["id","area_id","name","canonical_name","description","category","subcategory","quantity","location","placement","condition","brand","model","attributes","visible_text","aliases","note","issue_ids","previous_item_id","evidence","verification","confidence"],"properties":{"id":{"type":"string"},"area_id":{"type":"string"},"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"description":{"type":["string","null"],"description":"What it is, where it is with the viewpoint, what was said about it (use, settings, quirks, what to watch out for), its condition when notable, in one paragraph of 1-3 sentences in the report language. Never a sensitive value. Null at the base level and on app spaces."},"category":{"anyOf":[{"$ref":"#/components/schemas/ItemCategory"},{"type":"null"}]},"subcategory":{"type":["string","null"]},"quantity":{"type":["integer","null"],"minimum":1},"location":{"type":["string","null"],"description":"Where it is inside the area, as one sentence that carries the viewpoint when left and right matter: \"on the wall left of the entrance door, facing the door from inside; on your right as you enter\"."},"placement":{"anyOf":[{"$ref":"#/components/schemas/SpacePlacement"},{"type":"null"}],"description":"The same place as anchor + relation + viewpoint. Null at the base level and on app spaces."},"condition":{"anyOf":[{"$ref":"#/components/schemas/SpaceCondition"},{"type":"null"}]},"brand":{"type":["string","null"]},"model":{"type":["string","null"]},"attributes":{"type":"array","items":{"type":"string"}},"visible_text":{"type":"array","items":{"type":"string"}},"aliases":{"type":"array","items":{"type":"string"}},"note":{"type":["string","null"]},"issue_ids":{"type":"array","items":{"type":"string"}},"previous_item_id":{"type":["string","null"],"description":"The same object in the previous scan (`continuity.previous_scan_id`), when this scan was checked against it."},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}},"verification":{"anyOf":[{"$ref":"#/components/schemas/SpaceVerification"},{"type":"null"}],"description":"Checked again on its frames. Null at the base level, on documents made before verification existed, and where it could not be computed."},"confidence":{"type":["number","null"],"minimum":0,"maximum":1}}},"SpaceIssue":{"type":"object","required":["id","area_id","item_id","title","description","category","severity","suggested_action","previous_issue_id","evidence","verification","confidence"],"properties":{"id":{"type":"string"},"area_id":{"type":["string","null"]},"item_id":{"type":["string","null"]},"title":{"type":"string"},"description":{"type":["string","null"]},"category":{"anyOf":[{"$ref":"#/components/schemas/IssueCategory"},{"type":"null"}]},"severity":{"anyOf":[{"$ref":"#/components/schemas/IssueSeverity"},{"type":"null"}]},"suggested_action":{"type":["string","null"]},"previous_issue_id":{"type":["string","null"],"description":"The same issue in the previous scan, still there."},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}},"verification":{"anyOf":[{"$ref":"#/components/schemas/SpaceVerification"},{"type":"null"}],"description":"Checked again on its frames. Null at the base level, on documents made before verification existed, and where it could not be computed."},"confidence":{"type":["number","null"],"minimum":0,"maximum":1}}},"SpaceKeyLocation":{"type":"object","required":["id","kind","label","description","area_id","location","placement","notes","evidence","verification","confidence"],"properties":{"id":{"type":"string"},"kind":{"$ref":"#/components/schemas/KeyLocationKind"},"label":{"type":"string"},"description":{"type":["string","null"],"description":"What it is, where it is with the viewpoint, what was said about it (how to reach or operate it, what to watch out for), its condition when notable, in one paragraph. Never a code or a password. Null at the base level and on app spaces."},"area_id":{"type":["string","null"]},"location":{"type":["string","null"],"description":"Specific enough to walk straight to it, with the viewpoint when left and right matter."},"placement":{"anyOf":[{"$ref":"#/components/schemas/SpacePlacement"},{"type":"null"}],"description":"The same place as anchor + relation + viewpoint. Null at the base level and on app spaces."},"notes":{"type":["string","null"]},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}},"verification":{"anyOf":[{"$ref":"#/components/schemas/SpaceVerification"},{"type":"null"}],"description":"Checked again on its frames. Null at the base level, on documents made before verification existed, and where it could not be computed."},"confidence":{"type":["number","null"],"minimum":0,"maximum":1}}},"SpaceInstruction":{"type":"object","required":["id","title","area_id","item_id","steps","source_quote","transcript_segment_ids","confidence"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"area_id":{"type":["string","null"]},"item_id":{"type":["string","null"]},"steps":{"type":"array","items":{"type":"string"}},"source_quote":{"type":["string","null"]},"transcript_segment_ids":{"type":"array","items":{"type":"string"}},"confidence":{"type":["number","null"],"minimum":0,"maximum":1}}},"SpaceFact":{"type":"object","required":["id","kind","statement","value","area_id","sensitive","redacted","transcript_segment_ids","evidence","confidence"],"properties":{"id":{"type":"string"},"kind":{"$ref":"#/components/schemas/FactKind"},"statement":{"type":"string"},"value":{"type":["string","null"]},"area_id":{"type":["string","null"]},"sensitive":{"type":"boolean"},"redacted":{"type":"boolean"},"transcript_segment_ids":{"type":"array","items":{"type":"string"}},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}},"confidence":{"type":["number","null"],"minimum":0,"maximum":1}}},"SpaceQA":{"type":"object","required":["id","question","answer","source","category","area_id"],"properties":{"id":{"type":"string"},"question":{"type":"string"},"answer":{"type":"string"},"source":{"type":"string","enum":["owner","generated"]},"category":{"type":["string","null"]},"area_id":{"type":["string","null"]}}},"SpaceTranscriptSegment":{"type":"object","required":["id","start_ms","end_ms","text"],"properties":{"id":{"type":"string"},"start_ms":{"type":"integer"},"end_ms":{"type":"integer"},"text":{"type":"string"}}},"SpaceContinuity":{"type":"object","description":"How this scan was checked against the scan before it. Items and issues seen again carry `previous_item_id` / `previous_issue_id`; what is gone is listed only when a frame of this scan shows the place where it was. A previous record that is neither linked nor listed was not seen: it may be gone, or simply not filmed.","required":["previous_scan_id","absent_items","resolved_issues"],"properties":{"previous_scan_id":{"type":"string","pattern":"^scn_"},"absent_items":{"type":"array","items":{"type":"object","required":["item_id","name","note","evidence"],"properties":{"item_id":{"type":"string","description":"Id in the previous scan."},"name":{"type":"string","description":"Its name in the previous scan."},"note":{"type":["string","null"]},"evidence":{"type":"array","description":"Frames of this scan that show the place.","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}}}}},"resolved_issues":{"type":"array","items":{"type":"object","required":["issue_id","title","note","evidence"],"properties":{"issue_id":{"type":"string","description":"Id in the previous scan."},"title":{"type":"string"},"note":{"type":["string","null"]},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}}}}}}},"SpaceDocument":{"type":"object","required":["object","schema_version","space","scan","enrichment","summary","confidence","stats","features","areas","items","issues","key_locations","instructions","facts","qa","frames","continuity","coverage"],"properties":{"object":{"type":"string","enum":["space_document"]},"schema_version":{"type":"string","enum":["1.0"]},"space":{"type":"object","required":["id","source","name","description","space_type","created_at","updated_at"],"properties":{"id":{"type":"string","pattern":"^(app|spc)_"},"source":{"type":"string","enum":["app","api"]},"name":{"type":"string"},"description":{"type":["string","null"]},"space_type":{"$ref":"#/components/schemas/SpaceType"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"scan":{"type":"object","required":["id","analyzed_at","recorded_at","duration_seconds","device","video_sha256","frame_count"],"properties":{"id":{"type":"string","pattern":"^scn_"},"analyzed_at":{"type":["string","null"],"format":"date-time"},"recorded_at":{"type":["string","null"],"format":"date-time"},"duration_seconds":{"type":["number","null"]},"device":{"type":["string","null"]},"video_sha256":{"type":["string","null"]},"frame_count":{"type":["integer","null"]}}},"enrichment":{"type":"object","required":["level","status","updated_at"],"properties":{"level":{"type":"string","enum":["base","deep"]},"status":{"type":"string","enum":["not_requested","pending","complete","failed"]},"updated_at":{"type":["string","null"],"format":"date-time"}}},"summary":{"type":["string","null"]},"confidence":{"type":["number","null"],"minimum":0,"maximum":1},"stats":{"type":"object","required":["areas","items","issues","key_locations","instructions","facts"],"properties":{"areas":{"type":"integer"},"items":{"type":"integer"},"issues":{"type":"integer"},"key_locations":{"type":"integer"},"instructions":{"type":"integer"},"facts":{"type":"integer"}}},"features":{"type":"array","items":{"type":"string"}},"areas":{"type":"array","items":{"$ref":"#/components/schemas/SpaceArea"}},"items":{"type":"array","items":{"$ref":"#/components/schemas/SpaceItem"}},"issues":{"type":"array","items":{"$ref":"#/components/schemas/SpaceIssue"}},"key_locations":{"type":"array","items":{"$ref":"#/components/schemas/SpaceKeyLocation"}},"instructions":{"type":"array","items":{"$ref":"#/components/schemas/SpaceInstruction"}},"facts":{"type":"array","items":{"$ref":"#/components/schemas/SpaceFact"}},"qa":{"type":"array","items":{"$ref":"#/components/schemas/SpaceQA"}},"transcript":{"type":"object","required":["text","segments"],"properties":{"text":{"type":["string","null"]},"segments":{"type":"array","items":{"$ref":"#/components/schemas/SpaceTranscriptSegment"}}}},"frames":{"type":"array","items":{"$ref":"#/components/schemas/SpaceFrame"}},"continuity":{"anyOf":[{"$ref":"#/components/schemas/SpaceContinuity"},{"type":"null"}]},"coverage":{"type":"object","required":["warnings","not_documented","checklist"],"properties":{"warnings":{"type":"array","items":{"type":"string"}},"not_documented":{"type":"array","items":{"type":"string"}},"checklist":{"type":"array","description":"Key locations every frame was checked for, seen or not: those of the space's type (`GET /capture-profiles`), in their order. Empty when the frames were not checked.","items":{"$ref":"#/components/schemas/SpaceCoverageCheck"}}}}}},"SpaceSummary":{"type":"object","required":["object","id","source","name","space_type","created_at","updated_at","latest_scan"],"properties":{"object":{"type":"string","enum":["space"]},"id":{"type":"string","pattern":"^(app|spc)_"},"source":{"type":"string","enum":["app","api"]},"name":{"type":"string"},"space_type":{"$ref":"#/components/schemas/SpaceType"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"latest_scan":{"anyOf":[{"type":"object","required":["id","analyzed_at","areas","items","issues"],"properties":{"id":{"type":"string","pattern":"^scn_"},"analyzed_at":{"type":["string","null"],"format":"date-time"},"areas":{"type":"integer"},"items":{"type":"integer"},"issues":{"type":"integer"}}},{"type":"null"}]}}},"SpaceListResponse":{"type":"object","required":["data","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SpaceSummary"}},"next_cursor":{"type":["string","null"]}}},"ScanSummary":{"type":"object","required":["object","id","space_id","analyzed_at","areas","items","issues","current"],"properties":{"object":{"type":"string","enum":["scan"]},"id":{"type":"string","pattern":"^scn_"},"space_id":{"type":"string","pattern":"^(app|spc)_"},"analyzed_at":{"type":["string","null"],"format":"date-time"},"areas":{"type":"integer"},"items":{"type":"integer"},"issues":{"type":"integer"},"current":{"type":"boolean"}}},"ScanListResponse":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ScanSummary"}}}},"SearchResults":{"type":"object","required":["object","query","data"],"properties":{"object":{"type":"string","const":"search_results"},"query":{"type":"string"},"data":{"type":"array","maxItems":25,"items":{"type":"object","required":["space_id","space_name","scan_id","record_type","record_id","title","area","text","score"],"properties":{"space_id":{"type":"string"},"space_name":{"type":"string"},"scan_id":{"type":"string"},"record_type":{"type":"string","enum":["area","item","issue","key_location","instruction","fact","qa"]},"record_id":{"type":"string"},"title":{"type":"string"},"area":{"type":["string","null"]},"text":{"type":"string"},"score":{"type":"number","description":"Relevance; higher is better. Comparable within one response only."}},"additionalProperties":false}}},"additionalProperties":false},"SpaceAnswer":{"type":"object","required":["object","space_id","scan_id","question","answer","documented","citations"],"properties":{"object":{"type":"string","const":"answer"},"space_id":{"type":"string"},"scan_id":{"type":"string"},"question":{"type":"string"},"answer":{"type":"string"},"documented":{"type":"boolean"},"citations":{"type":"array","items":{"type":"object","required":["record_type","record_id","title","area","evidence"],"properties":{"record_type":{"type":"string","enum":["area","item","issue","key_location","instruction","fact","qa"]},"record_id":{"type":"string"},"title":{"type":"string"},"area":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"]}}}}}}}}},"SkillFile":{"type":"object","required":["path","media_type","content"],"properties":{"path":{"type":"string"},"media_type":{"type":"string","enum":["text/markdown","application/json"]},"content":{"type":"string"}}},"SkillBundle":{"type":"object","required":["object","name","space_id","scan_id","files"],"properties":{"object":{"type":"string","const":"skill"},"name":{"type":"string","pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","maxLength":64},"space_id":{"type":"string"},"scan_id":{"type":"string"},"files":{"type":"array","items":{"$ref":"#/components/schemas/SkillFile"}}}},"SpaceDiff":{"type":"object","required":["object","space_id","from","to","level","linked","summary","areas","items","issues"],"properties":{"object":{"type":"string","const":"space_diff"},"space_id":{"type":"string"},"from":{"type":"object","required":["scan_id","analyzed_at"],"properties":{"scan_id":{"type":"string"},"analyzed_at":{"type":["string","null"],"format":"date-time"}}},"to":{"type":"object","required":["scan_id","analyzed_at"],"properties":{"scan_id":{"type":"string"},"analyzed_at":{"type":["string","null"],"format":"date-time"}}},"level":{"type":"string","enum":["base","deep"]},"linked":{"type":"boolean","description":"True when `to` was checked against `from` itself (consecutive scans of a space created through the API): its links and confirmed removals are used. Otherwise records are matched by content only, and nothing can be confirmed removed or resolved."},"summary":{"type":"object","required":["areas_added","areas_removed","items_added","items_removed","items_not_seen","items_moved","items_changed","issues_new","issues_resolved","issues_not_seen"],"properties":{"areas_added":{"type":"integer","minimum":0},"areas_removed":{"type":"integer","minimum":0},"items_added":{"type":"integer","minimum":0},"items_removed":{"type":"integer","minimum":0},"items_not_seen":{"type":"integer","minimum":0},"items_moved":{"type":"integer","minimum":0},"items_changed":{"type":"integer","minimum":0},"issues_new":{"type":"integer","minimum":0},"issues_resolved":{"type":"integer","minimum":0},"issues_not_seen":{"type":"integer","minimum":0}}},"areas":{"type":"object","required":["added","removed","condition_changed"],"properties":{"added":{"type":"array","items":{"type":"string"}},"removed":{"type":"array","items":{"type":"string"}},"condition_changed":{"type":"array","items":{"type":"object","required":["area","from","to"],"properties":{"area":{"type":"string"},"from":{"type":"string","enum":["new","excellent","good","fair","poor","damaged","not_working","unknown"]},"to":{"type":"string","enum":["new","excellent","good","fair","poor","damaged","not_working","unknown"]}}}}}},"items":{"type":"object","required":["added","removed","not_seen","moved","quantity_changed","condition_changed"],"properties":{"added":{"type":"array","items":{"type":"object","required":["name","canonical_name","area","quantity","evidence"],"properties":{"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"area":{"type":["string","null"]},"quantity":{"type":["integer","null"]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}}}}},"removed":{"type":"array","description":"Confirmed gone: a frame of `to` shows the place without it.","items":{"type":"object","required":["name","canonical_name","area","quantity","evidence","note"],"properties":{"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"area":{"type":["string","null"]},"quantity":{"type":["integer","null"]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}},"note":{"type":["string","null"]}}}},"not_seen":{"type":"array","description":"In `from`, not in `to`, not confirmed gone: it may be gone, or its place was not filmed.","items":{"type":"object","required":["name","canonical_name","area","quantity","evidence"],"properties":{"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"area":{"type":["string","null"]},"quantity":{"type":["integer","null"]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}}}}},"moved":{"type":"array","items":{"type":"object","required":["name","canonical_name","area","quantity","evidence","from_area"],"properties":{"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"area":{"type":["string","null"]},"quantity":{"type":["integer","null"]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}},"from_area":{"type":["string","null"]}}}},"quantity_changed":{"type":"array","items":{"type":"object","required":["name","canonical_name","area","quantity","evidence","from_quantity"],"properties":{"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"area":{"type":["string","null"]},"quantity":{"type":["integer","null"]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}},"from_quantity":{"type":"integer"}}}},"condition_changed":{"type":"array","items":{"type":"object","required":["name","canonical_name","area","quantity","evidence","from_condition","to_condition"],"properties":{"name":{"type":"string"},"canonical_name":{"type":["string","null"]},"area":{"type":["string","null"]},"quantity":{"type":["integer","null"]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}},"from_condition":{"type":"string","enum":["new","excellent","good","fair","poor","damaged","not_working","unknown"]},"to_condition":{"type":"string","enum":["new","excellent","good","fair","poor","damaged","not_working","unknown"]}}}}}},"issues":{"type":"object","required":["new","resolved","not_seen"],"properties":{"new":{"type":"array","items":{"type":"object","required":["title","area","severity","evidence"],"properties":{"title":{"type":"string"},"area":{"type":["string","null"]},"severity":{"type":["string","null"],"enum":["low","medium","high","critical",null]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}}}}},"resolved":{"type":"array","description":"Confirmed fixed: a frame of `to` shows the place without the defect.","items":{"type":"object","required":["title","area","severity","evidence","note"],"properties":{"title":{"type":"string"},"area":{"type":["string","null"]},"severity":{"type":["string","null"],"enum":["low","medium","high","critical",null]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}},"note":{"type":["string","null"]}}}},"not_seen":{"type":"array","description":"In `from`, not reported in `to`, not confirmed fixed.","items":{"type":"object","required":["title","area","severity","evidence"],"properties":{"title":{"type":"string"},"area":{"type":["string","null"]},"severity":{"type":["string","null"],"enum":["low","medium","high","critical",null]},"evidence":{"type":"object","description":"Frames of each scan that show the record. For a removal or a resolution, `to` shows the place without it.","required":["from","to"],"properties":{"from":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}},"to":{"type":"array","items":{"type":"object","required":["frame_id","timestamp_ms","url"],"properties":{"frame_id":{"type":"string"},"timestamp_ms":{"type":["integer","null"]},"url":{"type":["string","null"],"description":"Signed URL of the original frame; null when this key may not see it."}}}}}}}}}}}}},"Wallet":{"type":"object","required":["object","currency","balance_cents","free_scans_remaining","scan_price_cents","card_on_file","auto_recharge"],"properties":{"object":{"type":"string","const":"wallet"},"currency":{"type":"string","const":"usd"},"balance_cents":{"type":"integer"},"free_scans_remaining":{"type":"integer","minimum":0},"scan_price_cents":{"type":"integer","minimum":1},"card_on_file":{"type":"boolean","description":"A real card stands behind the account: a card saved in the console, a paid top-up, or a settled x402 payment on mainnet. Starting a live scan, and creating a live key that can scan, need it: without it they answer 402 `PAYMENT_METHOD_REQUIRED`. Adding a card charges nothing."},"tax_added":{"type":"boolean","description":"Whether tax is added on top of top-ups and automatic recharges where it applies (such as VAT), from the billing address and tax ID, and shown on the payment page and the invoice. A business with a valid VAT number in another EU country is invoiced under the reverse charge. The balance always receives the amount before tax."},"auto_recharge":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"threshold_cents":{"type":["integer","null"]},"amount_cents":{"type":["integer","null"]},"payment_method_saved":{"type":"boolean"},"last_error":{"type":["string","null"]}}},"terms_accepted":{"type":["object","null"],"required":["version","accepted_at"],"description":"Console only (absent from `GET /wallet`): the first time this account accepted the current version of the billing terms, with the box of a payment or card page; null if never, or only an older version. The box is still ticked on every page (`accept_terms`), and the first acceptance is kept on later payments for a dispute.","properties":{"version":{"type":"string"},"accepted_at":{"type":"string","format":"date-time"}}}}},"SpaceCreateRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":["string","null"],"maxLength":2000},"space_type":{"type":["string","null"],"enum":["apartment","house","office","warehouse","vehicle","boat","camper","shop","garage","appliance","other",null]},"external_id":{"type":["string","null"],"maxLength":128}}},"ApiSpace":{"type":"object","required":["object","id","source","livemode","name","description","space_type","external_id","created_at","updated_at"],"properties":{"object":{"type":"string","const":"space"},"id":{"type":"string","pattern":"^spc_[0-9a-f]{32}$"},"source":{"type":"string","const":"api"},"livemode":{"type":"boolean"},"name":{"type":"string"},"description":{"type":["string","null"]},"space_type":{"type":["string","null"]},"external_id":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"ScanCreateRequest":{"type":"object","required":["input"],"properties":{"input":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["frames","video"]},"language":{"type":"string","enum":["en","it","es","fr","de","pt","nl"],"default":"en","description":"The language the report is written in: area and item names, descriptions, issues, instructions, facts, questions and answers (`en` English, `it` Italian, `es` Spanish, `fr` French, `de` German, `pt` Portuguese, `nl` Dutch). Portuguese is written as in Brazil. The narration can be in any language. Spaces scanned with the iPhone app are in `en` or `it`."},"frames":{"type":"array","minItems":1,"maxItems":120,"items":{"type":"object","properties":{"timestamp_ms":{"type":["integer","null"],"minimum":0}}}},"video":{"type":"object","required":["content_type","content_length"],"properties":{"content_type":{"type":"string","enum":["video/mp4","video/quicktime","video/x-m4v","video/3gpp","video/webm","video/x-matroska","video/matroska"],"description":"The container of the file: MP4, MOV, M4V or 3GP (H.264, HEVC 8 or 10 bit, AV1, ProRes, MPEG-4 part 2, H.263; AAC, Opus, PCM, AMR or no audio; any frame rate; rotation metadata applied, fragmented MP4 included), WebM or Matroska. A WebM recorded in a browser (MediaRecorder) states no duration: where the API reads videos with its own ffmpeg (`limits.max_video_bytes` of `GET /v1` above 100 MiB) its length is measured and it is read like any other; elsewhere it fails with `VIDEO_UNREADABLE`, not charged (remux it first: `ffmpeg -i in.webm -c copy out.webm`). Any other type is refused here with 400 `INVALID_CONTENT_TYPE`."},"content_length":{"type":"integer","minimum":1,"maximum":4294967296,"description":"The size of the file in bytes. Up to 4294967296 (4 GiB) where the API reads videos with its own ffmpeg ingest, 104857600 (100 MiB) otherwise; `limits.max_video_bytes` of `GET /` says which. A test scan takes at most 99614720 (95 MiB): it uploads through the API. Larger is refused here with 400 `VALIDATION_ERROR`. Above 99614720 (95 MiB) the video can only be sent to the direct upload link (`upload.url`)."}}},"transcript":{"type":["object","null"],"properties":{"text":{"type":["string","null"]},"segments":{"type":"array","maxItems":600,"items":{"type":"object","required":["start_ms","end_ms","text"],"properties":{"start_ms":{"type":"integer"},"end_ms":{"type":"integer"},"text":{"type":"string"}}}}}},"recorded_at":{"type":["string","null"],"format":"date-time"},"device":{"type":["string","null"]},"duration_seconds":{"type":["number","null"]}}}}},"Scan":{"type":"object","required":["object","id","space_id","livemode","status","stage","input","charge","error","upload","created_at","started_at","completed_at"],"properties":{"object":{"type":"string","const":"scan"},"id":{"type":"string","pattern":"^scn_[0-9a-f]{32}$"},"space_id":{"type":"string"},"livemode":{"type":"boolean"},"status":{"type":"string","enum":["awaiting_upload","queued","processing","complete","failed","cancelled"]},"stage":{"type":["string","null"],"enum":["ingesting","screening","observing","fusing","enriching","saving","verifying",null]},"input":{"type":"object","required":["kind","language","frame_count","video","has_transcript"],"properties":{"kind":{"type":"string","enum":["frames","video"]},"language":{"type":"string","enum":["en","it","es","fr","de","pt","nl"],"description":"The language the report is written in."},"frame_count":{"type":["integer","null"]},"video":{"type":["object","null"],"properties":{"content_type":{"type":"string"},"content_length":{"type":"integer"}}},"has_transcript":{"type":"boolean"}}},"charge":{"type":"object","required":["kind","price_cents","refunded"],"properties":{"kind":{"type":["string","null"],"enum":["free_scan","scan_charge",null]},"price_cents":{"type":"integer"},"refunded":{"type":"boolean"}}},"error":{"type":["object","null"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}},"upload":{"type":["object","null"],"properties":{"kind":{"type":"string","enum":["frames","video"]},"method":{"type":"string","const":"PUT"},"url":{"type":"string"},"url_template":{"type":"string"},"headers":{"type":"object","description":"Headers the PUT must carry. Content-Length must equal the declared size (HTTP clients set it from the body).","additionalProperties":{"type":"string"}},"frame_count":{"type":"integer"},"max_bytes":{"type":"integer"},"content_types":{"type":"array","items":{"type":"string"}},"expires_at":{"type":"string","format":"date-time","description":"Direct upload links only: the link must be used (the PUT started) within 15 minutes; an upload begun in time completes. Past it, `GET /scans/{id}` (with `scans:write`) gives a fresh link while the scan waits for its upload."}}},"created_at":{"type":["string","null"],"format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"}}},"ScanUpload":{"type":"object","required":["object","scan_id","bytes"],"properties":{"object":{"type":"string","enum":["scan_frame","scan_video"]},"scan_id":{"type":"string"},"index":{"type":"integer"},"bytes":{"type":"integer"},"content_type":{"type":"string"}}},"FrameCheck":{"type":"object","required":["object","livemode","space_type","usable","quality","shows_space","area","key_locations"],"properties":{"object":{"type":"string","enum":["frame_check"]},"livemode":{"type":"boolean"},"space_type":{"type":["string","null"],"enum":["apartment","house","office","warehouse","vehicle","boat","camper","shop","garage","appliance","other",null],"description":"The `space_type` asked, or null."},"usable":{"type":"boolean","description":"Sharp, lit and showing the space: worth keeping in a scan."},"quality":{"type":"object","required":["score","level"],"properties":{"score":{"type":"number","minimum":0,"maximum":3,"description":"Expected level, 0 (unusable) to 3 (excellent)."},"level":{"type":"string","enum":["unusable","poor","good","excellent"]}}},"shows_space":{"type":"number","minimum":0,"maximum":1,"description":"Probability that the frame shows the real thing being filmed (a place, a vehicle, a boat, an appliance), not a drawing, document or screen."},"area":{"type":["object","null"],"description":"The most likely area of the space (`GET /capture-profiles`), with its probability: show it from about 0.6. Null without `space_type`, for a type with no areas (appliance), or when the frame shows none of them.","required":["value","probability"],"properties":{"value":{"type":"string","enum":["entrance","hallway","living_room","kitchen","dining_room","bedroom","bathroom","laundry","storage","balcony","garden","garage","stairs","exterior","reception","open_space","office_room","meeting_room","server_room","sales_floor","checkout","fitting_room","stockroom","loading_dock","storage_area","mezzanine","cab","living_area","deck","cockpit","cabin","galley","engine_room","exterior_front","exterior_rear","exterior_side","interior_front","interior_rear","trunk","engine_compartment"]},"probability":{"type":"number","minimum":0,"maximum":1}}},"key_locations":{"type":"array","description":"Every key location of the type of space, in its order.","items":{"type":"object","required":["kind","probability","seen"],"properties":{"kind":{"$ref":"#/components/schemas/KeyLocationKind"},"probability":{"type":"number","minimum":0,"maximum":1},"seen":{"type":"boolean","description":"Probability of at least 0.7."}}}}}},"CaptureProfile":{"type":"object","required":["object","space_type","language","key_locations","areas"],"properties":{"object":{"type":"string","enum":["capture_profile"]},"space_type":{"type":"string","enum":["apartment","house","office","warehouse","vehicle","boat","camper","shop","garage","appliance","other"]},"language":{"type":"string","enum":["en","it","es","fr","de","pt","nl"]},"key_locations":{"type":"array","description":"In the order to show them.","items":{"type":"object","required":["kind","label","hint","expected"],"properties":{"kind":{"$ref":"#/components/schemas/KeyLocationKind"},"label":{"type":"string"},"hint":{"type":"string","description":"What to film, in one line."},"expected":{"type":"boolean","description":"Usually there: worth a reminder when not filmed. Otherwise: if there is one."}}}},"areas":{"type":"array","description":"The areas `POST /frames/check?space_type=` can answer. Empty for an appliance.","items":{"type":"object","required":["value","label"],"properties":{"value":{"type":"string","enum":["entrance","hallway","living_room","kitchen","dining_room","bedroom","bathroom","laundry","storage","balcony","garden","garage","stairs","exterior","reception","open_space","office_room","meeting_room","server_room","sales_floor","checkout","fitting_room","stockroom","loading_dock","storage_area","mezzanine","cab","living_area","deck","cockpit","cabin","galley","engine_room","exterior_front","exterior_rear","exterior_side","interior_front","interior_rear","trunk","engine_compartment"]},"label":{"type":"string"}}}}}},"CaptureProfileList":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/CaptureProfile"}}}},"SpaceCoverageCheck":{"type":"object","required":["kind","seen","probability","evidence"],"properties":{"kind":{"$ref":"#/components/schemas/KeyLocationKind"},"seen":{"type":"boolean","description":"At least one frame shows it (probability from 0.7)."},"probability":{"type":"number","minimum":0,"maximum":1,"description":"The highest probability over the frames."},"evidence":{"type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/SpaceEvidenceRef"}}}},"DeviceStartRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":60,"description":"What is asking, shown to the person: \"CLI on MacBook\". Default \"CLI\"."},"mode":{"type":"string","enum":["live","test"],"description":"The mode of the key asked for (the person can change it). Default live."}}},"DeviceStart":{"type":"object","required":["device_code","user_code","verification_url","expires_in","interval"],"properties":{"device_code":{"type":"string","description":"The terminal's secret: never shown to the person."},"user_code":{"type":"string","example":"BCDF-2345"},"verification_url":{"type":"string","format":"uri","description":"The console page where the person types the code."},"expires_in":{"type":"integer"},"interval":{"type":"integer","description":"Seconds between polls."}}},"DeviceTokenRequest":{"type":"object","required":["device_code"],"properties":{"device_code":{"type":"string"}}},"DeviceToken":{"type":"object","required":["api_key","key","user"],"properties":{"api_key":{"type":"string","description":"The new key: shown once, store it."},"key":{"type":"object","required":["id","name","mode","prefix","scopes","monthly_cap_cents","created_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"mode":{"type":"string","enum":["live","test"]},"prefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"monthly_cap_cents":{"type":["integer","null"]},"created_at":{"type":"string"}}},"user":{"$ref":"#/components/schemas/ConsoleUser"}}},"AgentCreditRequest":{"type":"object","properties":{"amount_cents":{"type":"integer","minimum":100,"maximum":50000,"default":100,"description":"US cents to pay (and, on mainnet, to credit): US$1.00 is one live scan. x402 clients cap a payment at US$1 by default; raise their limit to pay more at once"},"rotate_key":{"type":"boolean","default":false,"description":"Make a new key and revoke the current one. Needs `Authorization: Bearer <the current key>`"}}},"AgentCredit":{"type":"object","required":["object","network","payer","transaction","livemode","amount_cents","balance_cents","api_key_prefix","notice","docs"],"properties":{"object":{"type":"string","enum":["agent_credit"]},"network":{"type":"string","enum":["eip155:8453","eip155:84532","solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"]},"payer":{"type":"string","pattern":"^(0x[0-9a-f]{40}|[1-9A-HJ-NP-Za-km-z]{32,44})$","description":"Lowercase on Base; base58 as written on Solana"},"transaction":{"type":"string","pattern":"^(0x[0-9a-f]{64}|[1-9A-HJ-NP-Za-km-z]{64,88})$","description":"The transaction hash (Base) or signature (Solana)"},"livemode":{"type":"boolean","description":"true on mainnet: live credit and a live key"},"amount_cents":{"type":"integer"},"balance_cents":{"type":["integer","null"],"description":"The wallet after this payment; null on testnet (nothing is credited)"},"api_key":{"type":"string","pattern":"^sc_(live|test)_","description":"Only when the key was created by this call: store it, it is not shown again"},"api_key_prefix":{"type":["string","null"],"description":"The account's key of this mode; null when it has none"},"notice":{"type":"string","description":"What to do with the key (store it now, or use the one stored)"},"docs":{"type":"string","format":"uri"}}},"X402PaymentRequirements":{"type":"object","required":["scheme","network","amount","asset","payTo","maxTimeoutSeconds","extra"],"properties":{"scheme":{"type":"string","enum":["exact"]},"network":{"type":"string","enum":["eip155:8453","eip155:84532","solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"],"description":"CAIP-2: Base (`eip155:…`) or Solana (`solana:…`)"},"amount":{"type":"string","pattern":"^\\d+$","description":"Atomic units of the asset (USDC: 6 decimals)"},"asset":{"type":"string","pattern":"^(0x[0-9a-fA-F]{40}|[1-9A-HJ-NP-Za-km-z]{32,44})$","description":"The USDC contract (Base) or mint (Solana)"},"payTo":{"type":"string","pattern":"^(0x[0-9a-fA-F]{40}|[1-9A-HJ-NP-Za-km-z]{32,44})$","description":"Who is paid: on Solana, the owner of the USDC account the transfer goes to"},"maxTimeoutSeconds":{"type":"integer"},"extra":{"anyOf":[{"type":"object","required":["name","version"],"properties":{"name":{"type":"string"},"version":{"type":"string"}},"description":"Base: the EIP-712 domain of USDC"},{"type":"object","required":["feePayer"],"properties":{"feePayer":{"type":"string","pattern":"^(0x[0-9a-fA-F]{40}|[1-9A-HJ-NP-Za-km-z]{32,44})$"}},"description":"Solana: the facilitator account that pays the network fee (the transaction's fee payer)"}]}}},"X402PaymentRequired":{"type":"object","required":["x402Version","resource","accepts"],"properties":{"x402Version":{"type":"integer","enum":[2]},"error":{"type":"string"},"resource":{"type":"object","required":["url","description","mimeType"],"properties":{"url":{"type":"string","format":"uri"},"description":{"type":"string"},"mimeType":{"type":"string"}}},"accepts":{"type":"array","items":{"$ref":"#/components/schemas/X402PaymentRequirements"}},"extensions":{"type":"object","description":"`bazaar`: how to call this resource and what it returns, for x402 catalogs"}}},"AgentPaymentError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"request_id":{"type":"string"},"reason":{"type":"string","description":"Why the payment was refused (x402 reason)"},"payment_required":{"$ref":"#/components/schemas/X402PaymentRequired"},"transaction":{"type":"string","description":"On 500: the settled transaction, for reconciliation"},"network":{"type":"string"},"nonce":{"type":"string","description":"On 502 (Base): the authorization nonce, for reconciliation"},"signature":{"type":"string","description":"On 502 (Solana): the payer's transaction signature, for reconciliation"}}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"request_id":{"type":"string"},"retry_after":{"type":"integer","description":"On a 429: seconds to wait, as in `Retry-After`."},"daily_limit":{"type":"integer","description":"On a 429 for a daily allowance: the allowance (it resets at midnight UTC)."},"max_streams":{"type":"integer","description":"On a 429 from `GET /scans/{id}/events`: the streams an account may hold open at once."},"max_open_scans":{"type":"integer","description":"On a 429 from `POST /spaces/{id}/scans`: the scans an account may hold created and not started."}},"additionalProperties":true}}}}}}