Base URL https://api.spacecontext.app/v1 · 29 operations · JSON in snake_case, timestamps ISO 8601 (UTC), money in US cents. Authenticate with Authorization: Bearer sc_live_… (an API key) or, for the console operations, a console session. Errors come as { "error": { "code", "message", "request_id" } }: see Errors.
Machine-readable contract: openapi.json · typed client: @spacecontext/sdk
Spaces
Spaces and their documents: create, list, read, search within, ask, export, compare, delete.
/spaces List the spaces this key can read: scanned with the app (live keys) and created through the API
API keyParameters
| Name | In · type | Description |
|---|---|---|
limit | query · integer (≥ 1, ≤ 50) | |
cursor | query · string | |
source | query · string: "app" | "api" | Only spaces from this source. Omitted: every space this key can read (app spaces need a live key; test keys see the demo space). |
Responses
SpaceListResponse Error Error Error Error Example 200 response
{
"data": [
{
"object": "space",
"id": "spc_3f9a…",
"source": "app",
"name": "Seaside flat",
"space_type": "apartment",
"created_at": "2026-10-05T13:54:37.000Z",
"updated_at": "2026-10-05T13:57:02.000Z",
"latest_scan": {
"id": "spc_3f9a…",
"analyzed_at": "2026-10-05T13:57:02.000Z",
"areas": 1,
"items": 1,
"issues": 1
}
}
],
"next_cursor": "string"
} /spaces Create a space to scan through the API
API keyCreates 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.
Request body application/json
{
"name": "Seaside flat",
"description": "…",
"space_type": "apartment",
"external_id": "ext_…"
} Responses
ApiSpace ApiSpace Error Error Error Error Example 200 response
{
"object": "space",
"id": "spc_3f9a…",
"source": "api",
"livemode": true,
"name": "Seaside flat",
"description": "…",
"space_type": "string",
"external_id": "ext_…",
"created_at": "2026-10-05T13:54:37.000Z",
"updated_at": "2026-10-05T13:57:02.000Z"
} Example 201 response
{
"object": "space",
"id": "spc_3f9a…",
"source": "api",
"livemode": true,
"name": "Seaside flat",
"description": "…",
"space_type": "string",
"external_id": "ext_…",
"created_at": "2026-10-05T13:54:37.000Z",
"updated_at": "2026-10-05T13:57:02.000Z"
} /spaces/{id} Get the current document for a space
API keyKeys 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) | |
include | query · string: "transcript" | Adds the transcript. Requires the spaces:sensitive scope; without it the request is refused with 403 INSUFFICIENT_SCOPE. |
enrich | query · string: "auto" | "none" |
Responses
SpaceDocument Error Error Error Error Error Example 200 response
{
"object": "space_document",
"schema_version": "1.0",
"space": {
"id": "spc_3f9a…",
"source": "app",
"name": "Seaside flat",
"description": "…",
"space_type": "apartment",
"created_at": "2026-10-05T13:54:37.000Z",
"updated_at": "2026-10-05T13:57:02.000Z"
},
"scan": {
"id": "spc_3f9a…",
"analyzed_at": "2026-10-05T13:57:02.000Z",
"recorded_at": "2026-10-05T13:57:02.000Z",
"duration_seconds": 1,
"device": "string",
"video_sha256": "string",
"frame_count": 1
},
"enrichment": {
"level": "base",
"status": "not_requested",
"updated_at": "2026-10-05T13:57:02.000Z"
},
"summary": "…",
"confidence": 0,
"stats": {
"areas": 1,
"items": 1,
"issues": 1,
"key_locations": 1,
"instructions": 1,
"facts": 1
},
"features": [
"string"
],
"areas": [
{
"id": "spc_3f9a…",
"name": "Seaside flat",
"type": "entrance",
"level": "string",
"condition": {
"code": "new",
"label": "…",
"notes": "string"
},
"description": "…",
"layout": "string",
"orientation": "string",
"features": [
"string"
],
"access_from": [
"string"
],
"item_ids": [
"string"
],
"issue_ids": [
"string"
],
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"confidence": 0
}
],
"items": [
{
"id": "spc_3f9a…",
"area_id": "are_…",
"name": "Seaside flat",
"canonical_name": "string",
"description": "…",
"category": "furniture",
"subcategory": "string",
"quantity": 1,
"location": "string",
"placement": {
"anchor": "string",
"anchor_item_id": "anc_…",
"relation": "on",
"viewpoint": "string",
"detail": "string"
},
"condition": {
"code": "new",
"label": "…",
"notes": "string"
},
"brand": "string",
"model": "string",
"attributes": [
"string"
],
"visible_text": [
"string"
],
"aliases": [
"string"
],
"note": "string",
"issue_ids": [
"string"
],
"previous_item_id": "pre_…",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"verification": {
"status": "verified",
"probability": 0,
"frame_id": "fra_…"
},
"confidence": 0
}
],
"issues": [
{
"id": "spc_3f9a…",
"area_id": "are_…",
"item_id": "ite_…",
"title": "…",
"description": "…",
"category": "damage",
"severity": "low",
"suggested_action": "string",
"previous_issue_id": "pre_…",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"verification": {
"status": "verified",
"probability": 0,
"frame_id": "fra_…"
},
"confidence": 0
}
],
"key_locations": [
{
"id": "spc_3f9a…",
"kind": "electrical_panel",
"label": "…",
"description": "…",
"area_id": "are_…",
"location": "string",
"placement": {
"anchor": "string",
"anchor_item_id": "anc_…",
"relation": "on",
"viewpoint": "string",
"detail": "string"
},
"notes": "string",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"verification": {
"status": "verified",
"probability": 0,
"frame_id": "fra_…"
},
"confidence": 0
}
],
"instructions": [
{
"id": "spc_3f9a…",
"title": "…",
"area_id": "are_…",
"item_id": "ite_…",
"steps": [
"string"
],
"source_quote": "string",
"transcript_segment_ids": [
"string"
],
"confidence": 0
}
],
"facts": [
{
"id": "spc_3f9a…",
"kind": "access",
"statement": "…",
"value": "string",
"area_id": "are_…",
"sensitive": false,
"redacted": false,
"transcript_segment_ids": [
"string"
],
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"confidence": 0
}
],
"qa": [
{
"id": "spc_3f9a…",
"question": "…",
"answer": "…",
"source": "owner",
"category": "string",
"area_id": "are_…"
}
],
"transcript": {
"text": "…",
"segments": [
{
"id": "spc_3f9a…",
"start_ms": 1200,
"end_ms": 1200,
"text": "…"
}
]
},
"frames": [
{
"id": "spc_3f9a…",
"index": 1,
"timestamp_ms": 1200,
"url": "https://…",
"url_expires_at": "2026-10-05T13:57:02.000Z"
}
],
"continuity": {
"previous_scan_id": "pre_…",
"absent_items": [
{
"item_id": "ite_…",
"name": "Seaside flat",
"note": "string",
"evidence": [
{
"frame_id": null,
"timestamp_ms": null
}
]
}
],
"resolved_issues": [
{
"issue_id": "iss_…",
"title": "…",
"note": "string",
"evidence": [
{
"frame_id": null,
"timestamp_ms": null
}
]
}
]
},
"coverage": {
"warnings": [
"string"
],
"not_documented": [
"string"
],
"checklist": [
{
"kind": "electrical_panel",
"seen": true,
"probability": 0,
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
]
}
]
}
} /spaces/{id} Delete a space created through the API, with its scans and files
API keyNot possible while one of its scans is processing (409). App spaces are deleted in the app. Needs scans:write.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) |
Responses
Error Error Error Error Error /spaces/{id}/scans List scans for a space
API keyParameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) |
Responses
ScanListResponse Error Error Error Error Example 200 response
{
"data": [
{
"object": "scan",
"id": "spc_3f9a…",
"space_id": "spc_3f9a…",
"analyzed_at": "2026-10-05T13:57:02.000Z",
"areas": 1,
"items": 1,
"issues": 1,
"current": true
}
]
} /spaces/{id}/scans Create a scan of a space created through the API
API keyDeclares 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) | |
Idempotency-Key | header · string (pattern ^[A-Za-z0-9_-]{8,64}$) |
Request body application/json
{
"input": {
"kind": "frames",
"language": "en",
"frames": [
{
"timestamp_ms": 1200
}
],
"video": {
"content_type": "video/mp4",
"content_length": 1
},
"transcript": {
"text": "…",
"segments": [
{
"start_ms": 1200,
"end_ms": 1200,
"text": "…"
}
]
},
"recorded_at": "2026-10-05T13:57:02.000Z",
"device": "string",
"duration_seconds": 1
}
} Responses
Scan Scan Error Error Error Error Error Error Error Error Example 200 response
{
"object": "scan",
"id": "spc_3f9a…",
"space_id": "spc_3f9a…",
"livemode": true,
"status": "awaiting_upload",
"stage": "ingesting",
"input": {
"kind": "frames",
"language": "en",
"frame_count": 1,
"video": {
"content_type": "string",
"content_length": 1
},
"has_transcript": true
},
"charge": {
"kind": "free_scan",
"price_cents": 100,
"refunded": false
},
"error": {
"code": "string",
"message": "string"
},
"upload": {
"kind": "frames",
"method": "PUT",
"url": "https://…",
"url_template": "string",
"headers": {},
"frame_count": 1,
"max_bytes": 1,
"content_types": [
"string"
],
"expires_at": "2026-10-05T13:57:02.000Z"
},
"created_at": "2026-10-05T13:54:37.000Z",
"started_at": "2026-10-05T13:54:40.000Z",
"completed_at": "2026-10-05T13:57:02.000Z"
} Example 201 response
{
"object": "scan",
"id": "spc_3f9a…",
"space_id": "spc_3f9a…",
"livemode": true,
"status": "awaiting_upload",
"stage": "ingesting",
"input": {
"kind": "frames",
"language": "en",
"frame_count": 1,
"video": {
"content_type": "string",
"content_length": 1
},
"has_transcript": true
},
"charge": {
"kind": "free_scan",
"price_cents": 100,
"refunded": false
},
"error": {
"code": "string",
"message": "string"
},
"upload": {
"kind": "frames",
"method": "PUT",
"url": "https://…",
"url_template": "string",
"headers": {},
"frame_count": 1,
"max_bytes": 1,
"content_types": [
"string"
],
"expires_at": "2026-10-05T13:57:02.000Z"
},
"created_at": "2026-10-05T13:54:37.000Z",
"started_at": "2026-10-05T13:54:40.000Z",
"completed_at": "2026-10-05T13:57:02.000Z"
} /spaces/{id}/scans/{scan_id} Get a base document for a specific scan
API keyA 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) | |
scan_idrequired | path · string (pattern ^scn_) | |
include | query · string: "transcript" | Adds the transcript. Requires the spaces:sensitive scope; without it the request is refused with 403 INSUFFICIENT_SCOPE. |
Responses
SpaceDocument Error Error Error Error Error Example 200 response
{
"object": "space_document",
"schema_version": "1.0",
"space": {
"id": "spc_3f9a…",
"source": "app",
"name": "Seaside flat",
"description": "…",
"space_type": "apartment",
"created_at": "2026-10-05T13:54:37.000Z",
"updated_at": "2026-10-05T13:57:02.000Z"
},
"scan": {
"id": "spc_3f9a…",
"analyzed_at": "2026-10-05T13:57:02.000Z",
"recorded_at": "2026-10-05T13:57:02.000Z",
"duration_seconds": 1,
"device": "string",
"video_sha256": "string",
"frame_count": 1
},
"enrichment": {
"level": "base",
"status": "not_requested",
"updated_at": "2026-10-05T13:57:02.000Z"
},
"summary": "…",
"confidence": 0,
"stats": {
"areas": 1,
"items": 1,
"issues": 1,
"key_locations": 1,
"instructions": 1,
"facts": 1
},
"features": [
"string"
],
"areas": [
{
"id": "spc_3f9a…",
"name": "Seaside flat",
"type": "entrance",
"level": "string",
"condition": {
"code": "new",
"label": "…",
"notes": "string"
},
"description": "…",
"layout": "string",
"orientation": "string",
"features": [
"string"
],
"access_from": [
"string"
],
"item_ids": [
"string"
],
"issue_ids": [
"string"
],
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"confidence": 0
}
],
"items": [
{
"id": "spc_3f9a…",
"area_id": "are_…",
"name": "Seaside flat",
"canonical_name": "string",
"description": "…",
"category": "furniture",
"subcategory": "string",
"quantity": 1,
"location": "string",
"placement": {
"anchor": "string",
"anchor_item_id": "anc_…",
"relation": "on",
"viewpoint": "string",
"detail": "string"
},
"condition": {
"code": "new",
"label": "…",
"notes": "string"
},
"brand": "string",
"model": "string",
"attributes": [
"string"
],
"visible_text": [
"string"
],
"aliases": [
"string"
],
"note": "string",
"issue_ids": [
"string"
],
"previous_item_id": "pre_…",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"verification": {
"status": "verified",
"probability": 0,
"frame_id": "fra_…"
},
"confidence": 0
}
],
"issues": [
{
"id": "spc_3f9a…",
"area_id": "are_…",
"item_id": "ite_…",
"title": "…",
"description": "…",
"category": "damage",
"severity": "low",
"suggested_action": "string",
"previous_issue_id": "pre_…",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"verification": {
"status": "verified",
"probability": 0,
"frame_id": "fra_…"
},
"confidence": 0
}
],
"key_locations": [
{
"id": "spc_3f9a…",
"kind": "electrical_panel",
"label": "…",
"description": "…",
"area_id": "are_…",
"location": "string",
"placement": {
"anchor": "string",
"anchor_item_id": "anc_…",
"relation": "on",
"viewpoint": "string",
"detail": "string"
},
"notes": "string",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"verification": {
"status": "verified",
"probability": 0,
"frame_id": "fra_…"
},
"confidence": 0
}
],
"instructions": [
{
"id": "spc_3f9a…",
"title": "…",
"area_id": "are_…",
"item_id": "ite_…",
"steps": [
"string"
],
"source_quote": "string",
"transcript_segment_ids": [
"string"
],
"confidence": 0
}
],
"facts": [
{
"id": "spc_3f9a…",
"kind": "access",
"statement": "…",
"value": "string",
"area_id": "are_…",
"sensitive": false,
"redacted": false,
"transcript_segment_ids": [
"string"
],
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
],
"confidence": 0
}
],
"qa": [
{
"id": "spc_3f9a…",
"question": "…",
"answer": "…",
"source": "owner",
"category": "string",
"area_id": "are_…"
}
],
"transcript": {
"text": "…",
"segments": [
{
"id": "spc_3f9a…",
"start_ms": 1200,
"end_ms": 1200,
"text": "…"
}
]
},
"frames": [
{
"id": "spc_3f9a…",
"index": 1,
"timestamp_ms": 1200,
"url": "https://…",
"url_expires_at": "2026-10-05T13:57:02.000Z"
}
],
"continuity": {
"previous_scan_id": "pre_…",
"absent_items": [
{
"item_id": "ite_…",
"name": "Seaside flat",
"note": "string",
"evidence": [
{
"frame_id": null,
"timestamp_ms": null
}
]
}
],
"resolved_issues": [
{
"issue_id": "iss_…",
"title": "…",
"note": "string",
"evidence": [
{
"frame_id": null,
"timestamp_ms": null
}
]
}
]
},
"coverage": {
"warnings": [
"string"
],
"not_documented": [
"string"
],
"checklist": [
{
"kind": "electrical_panel",
"seen": true,
"probability": 0,
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200
}
]
}
]
}
} /spaces/{id}/skill Export a space as a multi-file Agent Skill
API keyReturns 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) | |
include | query · string: "transcript" | Adds references/transcript.md. Requires the spaces:sensitive scope; without it the request is refused with 403 INSUFFICIENT_SCOPE. |
enrich | query · string: "auto" | "none" |
Responses
SkillBundle Error Error Error Error Error Error Example 200 response
{
"object": "skill",
"name": "Seaside flat",
"space_id": "spc_3f9a…",
"scan_id": "scn_8a21…",
"files": [
{
"path": "string",
"media_type": "text/markdown",
"content": "string"
}
]
} /spaces/{id}/markdown Export a space as one Markdown page
API keyThe 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) | |
include | query · string: "transcript" | Adds the transcript. Requires the spaces:sensitive scope; without it the request is refused with 403 INSUFFICIENT_SCOPE. |
enrich | query · string: "auto" | "none" |
Responses
text/markdown Error Error Error Error Error Error /spaces/{id}/ask Ask a question about a space
API keyAnswers 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) |
Request body application/json
{
"question": "…"
} Responses
SpaceAnswer Error Error Error Error Error Error Example 200 response
{
"object": "answer",
"space_id": "spc_3f9a…",
"scan_id": "scn_8a21…",
"question": "…",
"answer": "…",
"documented": true,
"citations": [
{
"record_type": "area",
"record_id": "rec_…",
"title": "…",
"area": "string",
"evidence": [
{
"frame_id": "fra_…",
"timestamp_ms": 1200,
"url": "https://…"
}
]
}
]
} /spaces/{id}/diff Compare two scans of a space
API keyWhat 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^(app|spc)_) | |
fromrequired | query · string (pattern ^scn_) | Id of the earlier scan. |
to | query · string (pattern ^scn_) | Id of the later scan. Defaults to the current one. |
Responses
SpaceDiff Error Error Error Error Error Example 200 response
{
"object": "space_diff",
"space_id": "spc_3f9a…",
"from": {
"scan_id": "scn_8a21…",
"analyzed_at": "2026-10-05T13:57:02.000Z"
},
"to": {
"scan_id": "scn_8a21…",
"analyzed_at": "2026-10-05T13:57:02.000Z"
},
"level": "base",
"linked": true,
"summary": {
"areas_added": 0,
"areas_removed": 0,
"items_added": 0,
"items_removed": 0,
"items_not_seen": 0,
"items_moved": 0,
"items_changed": 0,
"issues_new": 0,
"issues_resolved": 0,
"issues_not_seen": 0
},
"areas": {
"added": [
"string"
],
"removed": [
"string"
],
"condition_changed": [
{
"area": "string",
"from": "new",
"to": "new"
}
]
},
"items": {
"added": [
{
"name": "Seaside flat",
"canonical_name": "string",
"area": "string",
"quantity": 1,
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
}
}
],
"removed": [
{
"name": "Seaside flat",
"canonical_name": "string",
"area": "string",
"quantity": 1,
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
},
"note": "string"
}
],
"not_seen": [
{
"name": "Seaside flat",
"canonical_name": "string",
"area": "string",
"quantity": 1,
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
}
}
],
"moved": [
{
"name": "Seaside flat",
"canonical_name": "string",
"area": "string",
"quantity": 1,
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
},
"from_area": "string"
}
],
"quantity_changed": [
{
"name": "Seaside flat",
"canonical_name": "string",
"area": "string",
"quantity": 1,
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
},
"from_quantity": 1
}
],
"condition_changed": [
{
"name": "Seaside flat",
"canonical_name": "string",
"area": "string",
"quantity": 1,
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
},
"from_condition": "new",
"to_condition": "new"
}
]
},
"issues": {
"new": [
{
"title": "…",
"area": "string",
"severity": "low",
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
}
}
],
"resolved": [
{
"title": "…",
"area": "string",
"severity": "low",
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
},
"note": "string"
}
],
"not_seen": [
{
"title": "…",
"area": "string",
"severity": "low",
"evidence": {
"from": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
],
"to": [
{
"frame_id": null,
"timestamp_ms": null,
"url": null
}
]
}
}
]
}
} Scans
Declare a scan, upload its video or frames, start it, follow it live, cancel it.
/scans/{id} Status of a scan
API keyPoll 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^scn_[0-9a-f]{32}$) |
Responses
Scan Error Error Error Error Example 200 response
{
"object": "scan",
"id": "spc_3f9a…",
"space_id": "spc_3f9a…",
"livemode": true,
"status": "awaiting_upload",
"stage": "ingesting",
"input": {
"kind": "frames",
"language": "en",
"frame_count": 1,
"video": {
"content_type": "string",
"content_length": 1
},
"has_transcript": true
},
"charge": {
"kind": "free_scan",
"price_cents": 100,
"refunded": false
},
"error": {
"code": "string",
"message": "string"
},
"upload": {
"kind": "frames",
"method": "PUT",
"url": "https://…",
"url_template": "string",
"headers": {},
"frame_count": 1,
"max_bytes": 1,
"content_types": [
"string"
],
"expires_at": "2026-10-05T13:57:02.000Z"
},
"created_at": "2026-10-05T13:54:37.000Z",
"started_at": "2026-10-05T13:54:40.000Z",
"completed_at": "2026-10-05T13:57:02.000Z"
} /scans/{id} Cancel a scan that has not started
API keyNothing was charged; its uploads are deleted. Needs scans:write.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^scn_[0-9a-f]{32}$) |
Responses
Error Error Error Error Error /scans/{id}/events Follow a scan in real time
API keyA 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^scn_[0-9a-f]{32}$) |
Responses
text/event-stream Error Error Error Error /scans/{id}/frames/{index} Upload one frame of a frames scan
API keyThe 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^scn_[0-9a-f]{32}$) | |
indexrequired | path · integer (≥ 0, ≤ 119) |
Request body image/jpeg, image/png, image/webp
The raw bytes of the file.
Responses
ScanUpload Error Error Error Error Error Error Error Error Example 200 response
{
"object": "scan_frame",
"scan_id": "scn_8a21…",
"index": 1,
"bytes": 1,
"content_type": "string"
} /scans/{id}/video Upload the video of a video scan through the API
API keyOnly 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^scn_[0-9a-f]{32}$) |
Request body video/mp4, video/quicktime, video/x-m4v, video/3gpp, video/webm, video/x-matroska, video/matroska
The raw bytes of the file.
Responses
ScanUpload Error Error Error Error Error Error Error Error Example 200 response
{
"object": "scan_frame",
"scan_id": "scn_8a21…",
"index": 1,
"bytes": 1,
"content_type": "string"
} /scans/{id}/start Charge the scan and start the analysis
API keyChecks 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.
Parameters
| Name | In · type | Description |
|---|---|---|
idrequired | path · string (pattern ^scn_[0-9a-f]{32}$) |
Responses
Scan Scan Error Error Error Error Error Error Error Example 200 response
{
"object": "scan",
"id": "spc_3f9a…",
"space_id": "spc_3f9a…",
"livemode": true,
"status": "awaiting_upload",
"stage": "ingesting",
"input": {
"kind": "frames",
"language": "en",
"frame_count": 1,
"video": {
"content_type": "string",
"content_length": 1
},
"has_transcript": true
},
"charge": {
"kind": "free_scan",
"price_cents": 100,
"refunded": false
},
"error": {
"code": "string",
"message": "string"
},
"upload": {
"kind": "frames",
"method": "PUT",
"url": "https://…",
"url_template": "string",
"headers": {},
"frame_count": 1,
"max_bytes": 1,
"content_types": [
"string"
],
"expires_at": "2026-10-05T13:57:02.000Z"
},
"created_at": "2026-10-05T13:54:37.000Z",
"started_at": "2026-10-05T13:54:40.000Z",
"completed_at": "2026-10-05T13:57:02.000Z"
} Example 202 response
{
"object": "scan",
"id": "spc_3f9a…",
"space_id": "spc_3f9a…",
"livemode": true,
"status": "awaiting_upload",
"stage": "ingesting",
"input": {
"kind": "frames",
"language": "en",
"frame_count": 1,
"video": {
"content_type": "string",
"content_length": 1
},
"has_transcript": true
},
"charge": {
"kind": "free_scan",
"price_cents": 100,
"refunded": false
},
"error": {
"code": "string",
"message": "string"
},
"upload": {
"kind": "frames",
"method": "PUT",
"url": "https://…",
"url_template": "string",
"headers": {},
"frame_count": 1,
"max_bytes": 1,
"content_types": [
"string"
],
"expires_at": "2026-10-05T13:57:02.000Z"
},
"created_at": "2026-10-05T13:54:37.000Z",
"started_at": "2026-10-05T13:54:40.000Z",
"completed_at": "2026-10-05T13:57:02.000Z"
} Frames
Check one frame while a space is being filmed: usable or not, which key locations of its type it shows, and the area.
/frames/check Check one frame while filming
API keyFor 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).
Parameters
| Name | In · type | Description |
|---|---|---|
space_type | query · string: "apartment" | "house" | "office" | "warehouse" | "vehicle" | "boat" | "camper" | "shop" | "garage" | "appliance" | "other" | The type of space being filmed: its key locations, and the area question. |
Request body image/jpeg, image/png, image/webp
The raw bytes of the file.
Responses
FrameCheck Error Error Error Error Error Error Error Error Example 200 response
{
"object": "frame_check",
"livemode": true,
"space_type": "apartment",
"usable": true,
"quality": {
"score": 0,
"level": "unusable"
},
"shows_space": 0,
"area": {
"value": "entrance",
"probability": 0
},
"key_locations": [
{
"kind": "electrical_panel",
"probability": 0,
"seen": true
}
]
} Capture profiles
What to film for each type of space: its key locations, each with a hint, and the areas the frame check tells apart. Public, no key.
/capture-profiles What to film, for every type of space
No authFor 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 | In · type | Description |
|---|---|---|
language | query · string: "en" | "it" | "es" | "fr" | "de" | "pt" | "nl" | The language of the labels and hints: en, it, es, fr, de, pt, nl (default en). |
Responses
CaptureProfileList Error Example 200 response
{
"object": "list",
"data": [
{
"object": "capture_profile",
"space_type": "apartment",
"language": "en",
"key_locations": [
{
"kind": "electrical_panel",
"label": "…",
"hint": "string",
"expected": true
}
],
"areas": [
{
"value": "entrance",
"label": "…"
}
]
}
]
} /capture-profiles/{space_type} What to film, for one type of space
No authOne 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 | In · type | Description |
|---|---|---|
space_typerequired | path · string: "apartment" | "house" | "office" | "warehouse" | "vehicle" | "boat" | "camper" | "shop" | "garage" | "appliance" | "other" | |
language | query · string: "en" | "it" | "es" | "fr" | "de" | "pt" | "nl" | The language of the labels and hints: en, it, es, fr, de, pt, nl (default en). |
Responses
CaptureProfile Error Error Example 200 response
{
"object": "capture_profile",
"space_type": "apartment",
"language": "en",
"key_locations": [
{
"kind": "electrical_panel",
"label": "…",
"hint": "string",
"expected": true
}
],
"areas": [
{
"value": "entrance",
"label": "…"
}
]
} Search
Search every space the key can read, in any language.
/search Search across all your spaces
API keyFinds 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.
Parameters
| Name | In · type | Description |
|---|---|---|
qrequired | query · string (min 1, max 300) | |
space_id | query · string (pattern ^(app|spc)_) | Searches one space only. |
limit | query · integer (≥ 1, ≤ 25) |
Responses
SearchResults Error Error Error Error Error Error Example 200 response
{
"object": "search_results",
"query": "string",
"data": [
{
"space_id": "spc_3f9a…",
"space_name": "string",
"scan_id": "scn_8a21…",
"record_type": "area",
"record_id": "rec_…",
"title": "…",
"area": "string",
"text": "…",
"score": 1
}
]
} Media
Evidence frames, served through signed links.
/media/{key} Serve a signed v1 evidence frame
No authParameters
| Name | In · type | Description |
|---|---|---|
keyrequired | path · string (pattern ^(frames|api)/) | 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. |
tokenrequired | query · string | |
expiresrequired | query · integer |
Responses
image/jpeg Error Error Error Wallet and billing
Balance, top-ups through Stripe, automatic top-up, ledger and invoices.
/wallet What this key has to spend
API keyFor 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.
Responses
Wallet Error Error Error Example 200 response
{
"object": "wallet",
"currency": "usd",
"balance_cents": 100,
"free_scans_remaining": 0,
"scan_price_cents": 100,
"card_on_file": true,
"tax_added": true,
"auto_recharge": {
"enabled": true,
"threshold_cents": 100,
"amount_cents": 100,
"payment_method_saved": true,
"last_error": "string"
},
"terms_accepted": {
"version": "string",
"accepted_at": "2026-10-05T13:57:02.000Z"
}
} Account and console
Sign-in, the key in use, the console overview and the audit log.
/me Get the current API key user
API keyResponses
MeResponse Error Error Example 200 response
{
"user": {
"id": "spc_3f9a…",
"email": "you@example.com",
"is_pro": true
},
"key": {
"id": "spc_3f9a…",
"mode": "live",
"scopes": [
"spaces:read"
]
}
} /auth/device Start a device sign-in (the CLI)
No authA 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.
Request body application/json · optional
{
"name": "Seaside flat",
"mode": "live"
} Responses
DeviceStart Error Error Example 201 response
{
"device_code": "string",
"user_code": "BCDF-2345",
"verification_url": "string",
"expires_in": 1,
"interval": 1
} /auth/device/token Poll a device sign-in
No authAnswers 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.
Request body application/json
{
"device_code": "string"
} Responses
DeviceToken Error Error Error Example 200 response
{
"api_key": "string",
"key": {
"id": "spc_3f9a…",
"name": "Seaside flat",
"mode": "live",
"prefix": "string",
"scopes": [
"string"
],
"monthly_cap_cents": 100,
"created_at": "2026-10-05T13:54:37.000Z"
},
"user": {
"id": "spc_3f9a…",
"email": "you@example.com",
"name": "Seaside flat",
"is_pro": true,
"is_admin": true
}
} Service
The API itself.
/ API metadata
No authWhere the contract, the docs and the MCP server are, and the input limits of this deployment (limits).
Responses
ApiInfo Example 200 response
{
"name": "space-context-api",
"version": "v1",
"openapi": "string",
"docs": "string",
"llms": "string",
"llms_full": "string",
"mcp": "string",
"console": "string",
"limits": {
"max_video_bytes": 1,
"max_video_seconds": 1,
"max_photos": 1
}
} /openapi.json OpenAPI document
No authResponses
Other
/agent/credits Pay with x402 and get an API key (agents)
No authFor an agent with a wallet and no account: pay in USDC on Base or Solana with x402 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.
1. 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.
2. 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.
On 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.
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.
400 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.
Parameters
| Name | In · type | Description |
|---|---|---|
PAYMENT-SIGNATURE | header · string (max 16384) | 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). |
Request body application/json · optional
{
"amount_cents": 100,
"rotate_key": false
} Responses
AgentCredit AgentPaymentError AgentPaymentError AgentPaymentError Error Error AgentPaymentError AgentPaymentError Error Example 200 response
{
"object": "agent_credit",
"network": "eip155:8453",
"payer": "string",
"transaction": "string",
"livemode": true,
"amount_cents": 100,
"balance_cents": 100,
"api_key": "string",
"api_key_prefix": "string",
"notice": "string",
"docs": "string"
} Objects
The shapes most answers are made of. Every schema of the contract is in openapi.json and exported as a type by the SDK.
SpaceDocument
| Field | Type | Description |
|---|---|---|
objectrequired | string: "space_document" | |
schema_versionrequired | string: "1.0" | |
spacerequired | object | |
scanrequired | object | |
enrichmentrequired | object | |
summaryrequired | string | null | |
confidencerequired | number | null (≥ 0, ≤ 1) | |
statsrequired | object | |
featuresrequired | array of string | |
areasrequired | array of SpaceArea | |
itemsrequired | array of SpaceItem | |
issuesrequired | array of SpaceIssue | |
key_locationsrequired | array of SpaceKeyLocation | |
instructionsrequired | array of SpaceInstruction | |
factsrequired | array of SpaceFact | |
qarequired | array of SpaceQA | |
transcript | object | |
framesrequired | array of SpaceFrame | |
continuityrequired | SpaceContinuity | null | |
coveragerequired | object |
SpaceSummary
| Field | Type | Description |
|---|---|---|
objectrequired | string: "space" | |
idrequired | string (pattern ^(app|spc)_) | |
sourcerequired | string: "app" | "api" | |
namerequired | string | |
space_typerequired | SpaceType | |
created_atrequired | string (date-time) | |
updated_atrequired | string (date-time) | |
latest_scanrequired | object | null |
Scan
| Field | Type | Description |
|---|---|---|
objectrequired | "scan" | |
idrequired | string (pattern ^scn_[0-9a-f]{32}$) | |
space_idrequired | string | |
livemoderequired | boolean | |
statusrequired | string: "awaiting_upload" | "queued" | "processing" | "complete" | "failed" | "cancelled" | |
stagerequired | string | null: "ingesting" | "screening" | "observing" | "fusing" | "enriching" | "saving" | "verifying" | "null" | |
inputrequired | object | |
chargerequired | object | |
errorrequired | object | null | |
uploadrequired | object | null | |
created_atrequired | string | null (date-time) | |
started_atrequired | string | null (date-time) | |
completed_atrequired | string | null (date-time) |
ScanSummary
| Field | Type | Description |
|---|---|---|
objectrequired | string: "scan" | |
idrequired | string (pattern ^scn_) | |
space_idrequired | string (pattern ^(app|spc)_) | |
analyzed_atrequired | string | null (date-time) | |
areasrequired | integer | |
itemsrequired | integer | |
issuesrequired | integer | |
currentrequired | boolean |
SearchResults
| Field | Type | Description |
|---|---|---|
objectrequired | "search_results" | |
queryrequired | string | |
datarequired | array of object |
SpaceAnswer
| Field | Type | Description |
|---|---|---|
objectrequired | "answer" | |
space_idrequired | string | |
scan_idrequired | string | |
questionrequired | string | |
answerrequired | string | |
documentedrequired | boolean | |
citationsrequired | array of object |
SpaceDiff
| Field | Type | Description |
|---|---|---|
objectrequired | "space_diff" | |
space_idrequired | string | |
fromrequired | object | |
torequired | object | |
levelrequired | string: "base" | "deep" | |
linkedrequired | boolean | 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. |
summaryrequired | object | |
areasrequired | object | |
itemsrequired | object | |
issuesrequired | object |
SkillBundle
| Field | Type | Description |
|---|---|---|
objectrequired | "skill" | |
namerequired | string (max 64, pattern ^[a-z0-9]+(-[a-z0-9]+)*$) | |
space_idrequired | string | |
scan_idrequired | string | |
filesrequired | array of SkillFile |
FrameCheck
| Field | Type | Description |
|---|---|---|
objectrequired | string: "frame_check" | |
livemoderequired | boolean | |
space_typerequired | string | null: "apartment" | "house" | "office" | "warehouse" | "vehicle" | "boat" | "camper" | "shop" | "garage" | "appliance" | "other" | "null" | The space_type asked, or null. |
usablerequired | boolean | Sharp, lit and showing the space: worth keeping in a scan. |
qualityrequired | object | |
shows_spacerequired | number (≥ 0, ≤ 1) | Probability that the frame shows the real thing being filmed (a place, a vehicle, a boat, an appliance), not a drawing, document or screen. |
arearequired | object | null | 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. |
key_locationsrequired | array of object | Every key location of the type of space, in its order. |
Wallet
| Field | Type | Description |
|---|---|---|
objectrequired | "wallet" | |
currencyrequired | "usd" | |
balance_centsrequired | integer | |
free_scans_remainingrequired | integer (≥ 0) | |
scan_price_centsrequired | integer (≥ 1) | |
card_on_filerequired | boolean | 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 | boolean | 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_rechargerequired | object | |
terms_accepted | object | null | 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. |