Skip to content
Space Context Developers
  • For developers
  • api
  • video
  • json
  • sdk

Video to structured data API: turn a walkthrough into JSON

Turn a walkthrough video into structured JSON with one API: create, upload, start, follow. curl, CLI and SDK code, limits, the output fields and the price.

Updated 8 min read

The Space Context API turns a walkthrough video of a physical space into structured JSON. You send a video (MP4, MOV, M4V, 3GP, WebM or Matroska, up to 4 GB and 10 minutes) or up to 120 photos, and get back a Space Document: areas, items and where they are, condition, issues, key locations, how-to steps and facts, each citing the video frames it came from. It takes four calls (create a space, declare a scan, upload, start) and an event stream to follow; the CLI and the TypeScript SDK do all of it in one.

In short

  • Base URL https://api.spacecontext.app/v1, with an API key as a bearer token.
  • Flow: POST /spaces → POST /spaces/{id}/scans → PUT the video → POST /scans/{id}/start → GET /scans/{id}/events.
  • Frame extraction, transcription and frame screening run on the server: no ffmpeg on your side.
  • $1.00 per live scan from prepaid credit, after a free first one with a card on file; failed scans are refunded. Test keys run the whole flow for free and return the demo space.

What does a video-to-structured-data API have to do?

Turning a video into reliable JSON is more than captioning frames. A walkthrough has to be sampled, transcribed, cleaned of blurry frames, described, merged into one inventory and screened for passwords. Space Context runs each step as a stage you can watch:

StageWhat happens
ingesting36 to 120 frames are taken across the video (about one every 4 seconds) and the audio is transcribed, in whatever language is spoken
screeningBlurred, dark or covered frames are left out; the sharpest frame of each part of the video is analyzed
observingEvery usable frame is described
fusingThe observations become areas, items and issues
enrichingA deep pass structures everything, finds key locations, instructions and facts, and marks sensitive values
savingThe new version of the space is stored

The source video is deleted as soon as its frames are extracted; the frames are kept as evidence.

How do you turn a walkthrough video into JSON with curl?

Terminal
API=https://api.spacecontext.app/v1
AUTH="Authorization: Bearer $SPACECONTEXT_API_KEY"

1. Create a space.

Terminal
curl -X POST $API/spaces -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"name": "Seaside flat", "space_type": "apartment"}'

The answer carries the space id (spc_…). An optional external_id stores your own reference, such as a listing id.

2. Declare the scan, with the video’s type and its exact size in bytes:

Terminal
curl -X POST $API/spaces/spc_3f…/scans -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: flat-2026-10-05" \
  -d '{"input": {"kind": "video", "language": "en", "video": {"content_type": "video/mp4", "content_length": 48211983}}}'

The scan comes back as awaiting_upload, with upload instructions:

JSON
{
  "object": "scan", "id": "scn_8a…", "status": "awaiting_upload",
  "upload": {
    "kind": "video", "method": "PUT",
    "url": "https://…",
    "headers": { "Content-Type": "video/mp4", "Content-Length": "48211983" },
    "expires_at": "…"
  }
}

language (en, it, es, fr, de, pt or nl) sets the language of the document; the narration can be in any language. A retry with the same Idempotency-Key returns the scan already created. For photos, send "kind": "frames" with a list such as [{ "timestamp_ms": 0 }, { "timestamp_ms": 2000 }] and upload one PUT per photo.

3. Upload the video.

Terminal
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/mp4" --upload-file flat.mp4

When upload.url points to storage (a signed link, valid for 15 minutes: GET /scans/{id} signs a new one while the scan waits for its upload), do not send your API key; only this link takes a video over 95 MB. When it points to the API (…/v1/scans/{id}/video, up to 95 MB), send the Authorization header as usual. --upload-file streams the file from disk with its length, where --data-binary @flat.mp4 would read all of it into memory first.

4. Start the scan.

Terminal
curl -X POST $API/scans/scn_8a…/start -H "$AUTH" -H "Content-Type: application/json" -d '{}'

The answer is 202 with the scan queued and its charge: { "kind": "free_scan" | "scan_charge", "price_cents": 100, "refunded": false }. Calling it again answers 200 and charges nothing.

5. Follow it as Server-Sent Events:

Terminal
curl -N $API/scans/scn_8a…/events -H "$AUTH"
Text
event: scan
id: 1
data: {"object":"scan","id":"scn_8a…","status":"queued",…}

event: scan
id: 2
data: {"object":"scan","id":"scn_8a…","status":"processing","stage":"observing",…}

The stream sends the scan at once and at every change, pings every 15 seconds and closes after the final state. Polling GET /scans/{id} works too, and so do the signed scan.completed and scan.failed webhooks. When the scan is complete, GET /spaces/spc_3f… returns the document. The full reference is on the Scans page of the docs.

All five steps run for free with a test key: get a free test key, and any video you upload comes back as the demo space, at once.

Can you do it in one command?

With the CLI (Node.js 22.12 or later, nothing to install):

Terminal
npx spacecontext login                      # approve a live key in the console
npx spacecontext scan ./flat.mp4 --name "Seaside flat" --type apartment

scan creates the space, uploads, starts and follows the scan, printing each stage in words (Reading the video, Looking at the frames, Extracting the context…), then a summary of the space. --format json prints the whole document, --format markdown one page, and --format skill --out <dir> writes an Agent Skill folder. Sizes are checked before anything is created, and a failed upload cancels the scan with nothing charged. A folder of photos works the same way.

And from code, with the TypeScript SDK?

Terminal
npm install @spacecontext/sdk
TypeScript
import { SpaceContext } from '@spacecontext/sdk';

const sc = new SpaceContext();
const { spaceId, document } = await sc.scan('./flat.mp4', {
  name: 'Seaside flat',
  type: 'apartment',
  onProgress: (p) => console.log(p.type === 'scan' ? p.label : p.type),
});

One call creates the space, uploads, starts, follows the event stream and returns the document; format: 'markdown' or 'skill' adds that export. The key comes from SPACECONTEXT_API_KEY. A scan that fails throws a SpaceContextError whose code is the scan’s error (FOOTAGE_UNUSABLE, ANALYSIS_EMPTY…), and its charge is refunded. The SDK has no runtime dependencies and runs in Node.js 22.12+, Deno and Cloudflare Workers (file paths are Node only; elsewhere pass a Blob). The single steps are there too: spaces.create, scans.fromVideo, scans.wait.

What are the limits?

Limit
VideoMP4, MOV (including iPhone HEVC), M4V, 3GP, WebM or Matroska; up to 4 GB and 10 minutes
Video uploaded through the API (no direct link offered)95 MB; a bigger video goes to the direct link
PhotosUp to 120 per scan, JPEG, PNG or WebP, 2 MB each
Transcript you provide600 segments of up to 2,000 characters
Direct upload linkValid 1 hour
Time to start after creating the scan24 hours, then it is canceled and its uploads deleted
Scans processing at once10 per account (more on request)
New spaces / new scans200 / 500 per day per account, in each mode
Event streams open at once10 per account

Most phone videos go as they are: ten minutes of a default 1080p recording come to about 1.2 GB, and ten minutes of 4K at 30 fps to about 3 GB. GET /v1 states the limits of the API it answers for in limits. On a slow connection, upload less: with ffmpeg installed, npx spacecontext scan --compress re-encodes the video to a smaller H.264 file before uploading it, and a video over the limit is compressed to fit without asking. To do it yourself (the longer side at most 1280 pixels, portrait or landscape; about 10 MB a minute):

Terminal
ffmpeg -i in.mov -vf "scale='min(1280,iw)':'min(1280,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2" \
  -c:v libx264 -b:v 1200k -c:a aac -b:a 96k -ac 1 -movflags +faststart out.mp4

Walk slowly with the lights on, name each room as you enter it, say where the water shut-off and the fuse box are, and hold labels in view for a second: spoken instructions become how-to steps, and spoken codes become facts marked sensitive.

What comes back?

GET /v1/spaces/{id} returns the Space Document:

FieldContents
space, scanIdentity, type, when it was analyzed, duration, number of frames
enrichmentlevel (deep for API scans once complete) and status
summary, confidence, features, statsThe space as a whole
areasType, condition, description, the items and issues in each
itemsName, generic name, category, quantity, location, condition, brand, model, visible text, aliases
issuesTitle, description, severity, suggested action
key_locationsWater shut-off, electrical panel, boiler, router, smoke detector… and where each one is
instructions, facts, qaHow-to steps from the narration, facts (Wi-Fi, rules, schedules…), questions and answers
framesThe evidence frames, each with a signed URL that expires
continuityHow this scan was checked against the previous one
coverageWarnings, what the video did not show, and a checklist of the key locations of the type of space (eight for a home), each seen or not

For scale: the demo space’s check-out video lasts 35 seconds and produced 36 frames, 3 areas, 38 items, 4 issues, 2 key locations, 1 how-to and 3 facts. Here is one of its issues, exactly as recorded:

JSON
{
  "id": "iss_2",
  "area_id": "room-33614ab5-3",
  "item_id": "itm_32",
  "title": "Loose heater cable",
  "description": "The portable heater below the vanity has a loose cable and must not be used until repaired.",
  "category": "safety",
  "severity": "high",
  "suggested_action": "Stop using the heater and repair or replace its cable before use.",
  "previous_issue_id": null,
  "evidence": [
    { "frame_id": "frame-28", "timestamp_ms": 28087 },
    { "frame_id": "frame-30", "timestamp_ms": 30058 },
    { "frame_id": "frame-31", "timestamp_ms": 31044 }
  ],
  "confidence": 0.99
}

The demo is a real one-bedroom apartment in Brisbane (photos by Chris Olszewski (Kgbo), CC BY-SA 4.0; the narration is invented for the example). The same space is also served as one Markdown page (/markdown), an Agent Skill folder (/skill) and a comparison of two scans (/diff). Field by field: the Space Document reference.

How much does it cost?

Price
Test keysFree: the demo space, scans complete at once, no model is called
First live scan of a new accountFree, one free scan per card; adding a card charges nothing
Each live scan after that$1.00, charged when the scan starts, from a prepaid balance
Reading, search, questions, exportsIncluded
Failed scansRefunded to the balance automatically

Top-ups go from $10 to $500 by card through Stripe, the balance never expires, there is no subscription, and prices exclude applicable taxes. Without a card on file (or a top-up already paid), starting a live scan answers 402 PAYMENT_METHOD_REQUIRED. An agent with a crypto wallet can instead buy credit and a key in one call, paying USDC on Base or Solana: see how AI agents pay for API usage with x402. Plans side by side: pricing.

How do test keys work?

A test key (sc_test_…) runs the whole flow for free on any .mp4, .mov, .m4v, .3gp, .webm or .mkv file up to 4 GB: the scan completes at once, no model is called, and the result is the demo space’s document under the name you chose. Build and test the integration, CI included, before spending anything.

Terminal
npx spacecontext login --test
npx spacecontext scan ./tour.mp4 --name "Test flat"

Test and live data never mix: a test key sees only the demo space and spaces created with test keys.

What if a scan fails?

The scan becomes failed, with an error and charge.refunded: true, and the scan.failed webhook is sent. Codes: VIDEO_UNREADABLE, VIDEO_TOO_LONG, FOOTAGE_UNUSABLE (no usable frame), ANALYSIS_EMPTY (no space recognized), or a processing error. A start can also be refused before anything is charged: 409 UPLOAD_INCOMPLETE, 402 INSUFFICIENT_FUNDS, 402 SPENDING_CAP_REACHED (the key’s monthly cap) or 429 SCANS_IN_PROGRESS (ten scans already processing).

Try it on the demo first

Get a free test key, run the five curl calls or npx spacecontext scan against any video, and read the JSON that comes back. When it fits your product, add a card (nothing is charged) and scan a real space: the first one is free. What an agent then does with the document is the subject of the context layer for the physical world.

FAQ

What video formats and sizes does the API accept?

MP4, MOV (including iPhone HEVC), M4V, 3GP, WebM and Matroska, up to 4 GB and 10 minutes per scan, or up to 120 photos in JPEG, PNG or WebP of up to 2 MB each. A video over 95 MB is uploaded to a signed storage link. On a slow connection the CLI can re-encode a video to a smaller file before uploading it, when ffmpeg is installed.

How long does it take to turn a video into JSON?

A scan usually takes a few minutes. You can follow it as Server-Sent Events from GET /v1/scans/{id}/events, poll GET /v1/scans/{id}, or receive a signed scan.completed webhook.

How much does the video to JSON API cost?

Test keys are free and return the demo space. The first live scan of a new account is free once a card is on file (one per card); after that each live scan costs $1.00 from a prepaid balance, and failed scans are refunded. Prices exclude applicable taxes.

Do I need ffmpeg or my own transcription?

No. The API extracts the frames and transcribes the audio in whatever language is spoken. If you already have a transcript, you can send it with the scan.

What is in the JSON that comes back?

A Space Document: areas, items with location, quantity, condition, brand and model, issues with severity, key locations such as the water shut-off, how-to steps, facts, Q&A, coverage gaps, and the evidence frames every record cites.