---
title: "Video to structured data API: turn a walkthrough into JSON"
description: "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."
lang: en
date: 2026-10-11
updated: 2026-10-14
tags: ["api", "video", "json", "sdk"]
source_url: https://spacecontext.app/blog/video-to-structured-json-api/
---

# 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.

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:

| Stage | What happens |
| --- | --- |
| `ingesting` | 36 to 120 frames are taken across the video (about one every 4 seconds) and the audio is transcribed, in whatever language is spoken |
| `screening` | Blurred, dark or covered frames are left out; the sharpest frame of each part of the video is analyzed |
| `observing` | Every usable frame is described |
| `fusing` | The observations become areas, items and issues |
| `enriching` | A deep pass structures everything, finds key locations, instructions and facts, and marks sensitive values |
| `saving` | The 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?

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

**1. Create a space.**

```bash
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:

```bash
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.**

```bash
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.**

```bash
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:

```bash
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](https://spacecontext.app/developers/docs/api/scans/).

All five steps run for free with a test key: [get a free test key](https://spacecontext.app/developers/console/), 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):

```bash
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?

```bash
npm install @spacecontext/sdk
```

```ts
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 |
| --- | --- |
| Video | MP4, 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 |
| Photos | Up to 120 per scan, JPEG, PNG or WebP, 2 MB each |
| Transcript you provide | 600 segments of up to 2,000 characters |
| Direct upload link | Valid 1 hour |
| Time to start after creating the scan | 24 hours, then it is canceled and its uploads deleted |
| Scans processing at once | 10 per account (more on request) |
| New spaces / new scans | 200 / 500 per day per account, in each mode |
| Event streams open at once | 10 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):

```bash
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:

| Field | Contents |
| --- | --- |
| `space`, `scan` | Identity, type, when it was analyzed, duration, number of frames |
| `enrichment` | `level` (`deep` for API scans once complete) and `status` |
| `summary`, `confidence`, `features`, `stats` | The space as a whole |
| `areas` | Type, condition, description, the items and issues in each |
| `items` | Name, generic name, category, quantity, location, condition, brand, model, visible text, aliases |
| `issues` | Title, description, severity, suggested action |
| `key_locations` | Water shut-off, electrical panel, boiler, router, smoke detector… and where each one is |
| `instructions`, `facts`, `qa` | How-to steps from the narration, facts (Wi-Fi, rules, schedules…), questions and answers |
| `frames` | The evidence frames, each with a signed URL that expires |
| `continuity` | How this scan was checked against the previous one |
| `coverage` | Warnings, 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](https://spacecontext.app/developers/docs/api/space-document/).

## How much does it cost?

| | Price |
| --- | --- |
| Test keys | Free: the demo space, scans complete at once, no model is called |
| First live scan of a new account | Free, 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, exports | Included |
| Failed scans | Refunded 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](https://spacecontext.app/blog/x402-ai-agents-pay-for-api-usdc/). Plans side by side: [pricing](https://spacecontext.app/developers/#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.

```bash
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](https://spacecontext.app/developers/console/), 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](https://spacecontext.app/blog/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.
