Abodio Video Processing API
Extract comprehensive home inventory from walkthrough videos using AI. Create a session, upload a video, and receive structured data about every room, appliance, fixture, piece of furniture, electronics, and decor detected.
https://api.abodio.com/v1application/jsonv1How it works
- Create a session — Your backend calls
POST /v1/sessionswith your API key. You receive a session ID, upload URL, and upload token. - Upload a video — Either redirect the user to the hosted upload page, or upload directly via the signed URL or TUS protocol.
- Signal upload complete — Call
POST /v1/sessions/:id/upload-completeto trigger processing. - Receive results — Poll
GET /v1/sessions/:idor receive a webhook when processing finishes. Fetch spaces and assets from the results endpoints.
Authentication
The Abodio API uses API keys for server-to-server authentication. Include your key in the Authorization header as a Bearer token.
curl https://api.abodio.com/v1/sessions \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx"Two authentication modes
| Mode | Used by | Mechanism |
|---|---|---|
API Key | Your backend server | Authorization: Bearer sk_... header |
Upload Token | Client-side upload page | ?token=... query parameter |
The upload token is a single-use, session-scoped token returned when you create a session. It grants access only to that session's upload endpoints and is safe to pass to the client.
Session Lifecycle
Every session follows a predictable state machine. Understanding these states helps you build robust integrations.
| Status | Description | Transitions to |
|---|---|---|
created | Session created, waiting for video upload | processing, expired |
processing | Video uploaded, AI analysis in progress | completed, failed |
completed | Analysis finished, results available | Terminal |
failed | Processing error occurred | Terminal |
expired | Session timed out or was cancelled | Terminal |
API Reference
All session endpoints require API key authentication via the Authorization header.
/v1/sessionsCreate a new processing session. Returns a session ID, a signed upload URL for the video, and an upload token for the client-side upload page.
Request body
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
external_user_id | string | Required | — | Your unique identifier for the user. Max 255 characters. |
external_home_id | string | Optional | — | Your home/property ID. Returned in session responses and webhooks for correlation. |
external_space_ids | string[] | Optional | — | Array of your space/room IDs. Passed through for mapping detected spaces to your system. |
external_asset_ids | string[] | Optional | — | Array of your asset/item IDs. Passed through for mapping detected assets to your system. |
max_videos | integer | Optional | 1 | Maximum number of videos allowed in this session. |
max_duration_sec | integer | Optional | 600 | Maximum video duration in seconds. Hard cap at 600 (10 minutes). |
expires_in | integer | Optional | 86400 | Session time-to-live in seconds. Default is 24 hours. |
callback_url | string | Optional | — | Webhook URL. Receives POST when session completes, fails, or expires. |
success_url | string | Optional | — | Redirect the user here after successful upload. |
cancel_url | string | Optional | — | Redirect URL if the user cancels. |
failure_url | string | Optional | — | Redirect URL on upload failure. |
metadata | object | Optional | {} | Arbitrary key-value pairs attached to the session (max 10 KB serialized). Useful for linking to your internal records. |
bubble_version | string | Optional | — | Bubble app version for enum alignment. When provided, the API fetches your Bubble app's home types, space types, asset types, and subtypes so AI output conforms to your data model. Obtain the version from your Bubble deployment (e.g. "933ok"). |
Request
curl -X POST https://api.abodio.com/v1/sessions \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"external_user_id": "user_2kFj9x",
"external_home_id": "home_abc123",
"external_space_ids": ["space_kitchen_1", "space_living_2"],
"external_asset_ids": ["asset_fridge_1", "asset_sofa_2"],
"max_videos": 1,
"max_duration_sec": 300,
"callback_url": "https://yourapp.com/webhooks/abodio",
"success_url": "https://yourapp.com/upload/success",
"bubble_version": "933ok",
"metadata": {
"property_id": "prop_abc123"
}
}'Response 201 Created
{
"session_id": "f6414dd2-112b-43a8-9983-4570b29d195d",
"upload_url": "https://xxx.supabase.co/storage/v1/object/upload/sign/videos/...",
"upload_token": "44a0870c1b0adfdff123740af4b8dcc...",
"expires_at": "2026-02-12T20:50:39.839Z",
"success_url": "https://yourapp.com/upload/success",
"cancel_url": null,
"failure_url": null,
"constraints": {
"max_videos": 1,
"max_duration_sec": 300,
"max_file_size_bytes": 524288000,
"accepted_types": [
"video/mp4",
"video/quicktime",
"video/webm",
"video/x-msvideo",
"video/x-matroska"
]
}
}| Field | Type | Description |
|---|---|---|
session_id | string | Unique session identifier (UUID). |
upload_url | string | Signed URL for single-PUT upload. Expires with the session. |
upload_token | string | Token for authenticating the client upload page. Safe to expose to the browser. |
expires_at | string | ISO 8601 expiration timestamp. |
constraints | object | Upload limits: max file size, duration, accepted MIME types. |
/v1/sessions/:idRetrieve a session's current status and summary counts. Use this to poll for completion if you don't use webhooks.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Session UUID |
Request
curl https://api.abodio.com/v1/sessions/f6414dd2-112b-43a8-9983-4570b29d195d \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx"Response 200 OK
{
"session_id": "d7c05bfb-6eaa-4abb-8c64-d2e6e8e34c15",
"status": "completed",
"external_user_id": "user_2kFj9x",
"external_home_id": "home_abc123",
"external_space_ids": ["space_kitchen_1", "space_living_2"],
"external_asset_ids": ["asset_fridge_1", "asset_sofa_2"],
"max_videos": 1,
"max_duration_sec": 600,
"videos_count": 1,
"total_spaces": 12,
"total_assets": 49,
"success_url": "https://yourapp.com/upload/success",
"cancel_url": null,
"failure_url": null,
"callback_url": "https://yourapp.com/webhooks/abodio",
"metadata": { "property_id": "prop_abc123" },
"expires_at": "2026-02-16T22:32:20.236Z",
"created_at": "2026-02-15T22:32:20.279Z"
}/v1/sessions/:id/spacesGet all detected rooms and spaces from a completed session. Each space includes its type, confidence score, video timestamps, and detected features.
Request
curl https://api.abodio.com/v1/sessions/f6414dd2-.../spaces \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx"Response 200 OK
{
"spaces": [
{
"id": "803c7d95-a4c1-4b17-8af8-d88b7baf04f6",
"video_id": "7c708f9f-3ec7-4fc2-adf4-32f6ec3ce35d",
"space_type": "kitchen",
"space_subtype": null,
"label": "Kitchen",
"condition": "excellent",
"flooring_type": "tile",
"wall_finish": "painted drywall",
"confidence": null,
"timestamp_start_sec": 60,
"timestamp_end_sec": 70,
"features": [
"Island with dark countertop",
"Stainless steel refrigerator",
"Modern cabinetry",
"Tile flooring",
"Recessed lighting",
"Open to sunroom",
"Decorative items on counters",
"Framed artwork on walls"
]
},
{
"id": "3726ae0c-ed19-4998-8965-12cf45758736",
"video_id": "7c708f9f-3ec7-4fc2-adf4-32f6ec3ce35d",
"space_type": "living_room",
"space_subtype": null,
"label": "Living Room",
"condition": "good",
"flooring_type": "hardwood",
"wall_finish": "painted drywall",
"confidence": null,
"timestamp_start_sec": 10,
"timestamp_end_sec": 10,
"features": [
"Fireplace with dark surround",
"Mantel with decorative items",
"Large windows with curtains",
"Hardwood flooring",
"Upholstered furniture",
"Framed artwork",
"Ceiling with crown molding"
]
},
{
"id": "697fa63c-4bb4-4c75-8a3e-e7cb81cb2bdc",
"video_id": "7c708f9f-3ec7-4fc2-adf4-32f6ec3ce35d",
"space_type": "laundry",
"space_subtype": null,
"label": "Laundry Room",
"condition": "excellent",
"flooring_type": "unknown",
"wall_finish": "painted drywall",
"confidence": null,
"timestamp_start_sec": 90,
"timestamp_end_sec": 110,
"features": [
"Thermador washer/dryer",
"Stainless steel appliances",
"Modern fixtures",
"Utility space"
]
}
]
}Space object fields
| Field | Type | Description |
|---|---|---|
id | string | Unique space identifier (UUID). |
video_id | string | The video this space was detected in. |
space_type | string | Room type: kitchen, living_room, bedroom, bathroom, garage, laundry, office, sunroom, entryway, hallway, etc. |
space_subtype | string? | Optional qualifier: "media area", "master", "guest", "half", etc. |
label | string | AI-generated human-readable label, e.g. "Kitchen", "Foyer/Entry Hall", "Living Room - Media Wall". |
condition | string? | Overall condition: excellent, good, fair, or poor. |
flooring_type | string? | Detected flooring: "hardwood", "tile", "ceramic tile", "carpet", etc. |
wall_finish | string? | Detected wall finish: "painted drywall", "painted drywall with wainscoting", "painted drywall - blue accent walls", etc. |
confidence | number? | AI confidence score (0.0 to 1.0). May be null. |
timestamp_start_sec | number | Video timestamp (seconds) where this space first appears. |
timestamp_end_sec | number | Video timestamp (seconds) where this space ends. |
features | string[] | Notable features detected as descriptive strings, e.g. "Stainless steel refrigerator", "Recessed lighting", "Crown molding". |
/v1/sessions/:id/assetsGet all detected assets (appliances, fixtures, furniture, structural features, electronics, and decor) from a completed session. Each asset includes category, brand, model, condition, estimated value, and the space it belongs to.
Request
curl https://api.abodio.com/v1/sessions/f6414dd2-.../assets \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx"Response 200 OK
{
"assets": [
{
"id": "5a6bda63-67bd-4b09-8f1d-1e6048fc183a",
"video_id": "7c708f9f-3ec7-4fc2-adf4-32f6ec3ce35d",
"space_id": "db03956e-1ce7-4430-8218-4c4819b04fd7",
"category": "decor",
"subcategory": "rug",
"name": "Area Rug",
"brand": null,
"model": null,
"material": "wool or synthetic",
"color": "red with traditional pattern",
"dimensions": null,
"condition_rating": "good",
"condition_notes": null,
"estimated_value_usd": null,
"confidence": "high",
"detected_by": "claude-haiku-4.5",
"features": null,
"source": "ai_detected"
},
{
"id": "51a275bb-d8f8-45bc-a4c0-87facc781ae1",
"video_id": "7c708f9f-3ec7-4fc2-adf4-32f6ec3ce35d",
"space_id": "db03956e-1ce7-4430-8218-4c4819b04fd7",
"category": "structural",
"subcategory": "fireplace",
"name": "Fireplace",
"brand": null,
"model": null,
"material": "stone/brick with dark surround",
"color": "dark",
"dimensions": null,
"condition_rating": "good",
"condition_notes": null,
"estimated_value_usd": null,
"confidence": "high",
"detected_by": "claude-haiku-4.5",
"features": null,
"source": "ai_detected"
},
{
"id": "a89f2c41-3b7e-4d12-9e56-7f8a1b2c3d4e",
"video_id": "7c708f9f-3ec7-4fc2-adf4-32f6ec3ce35d",
"space_id": "803c7d95-a4c1-4b17-8af8-d88b7baf04f6",
"category": "appliance",
"subcategory": "refrigerator",
"name": "Stainless Steel Refrigerator",
"brand": null,
"model": null,
"material": "stainless steel",
"color": "silver",
"dimensions": null,
"condition_rating": "excellent",
"condition_notes": null,
"estimated_value_usd": null,
"confidence": "high",
"detected_by": "claude-haiku-4.5",
"features": null,
"source": "ai_detected"
}
]
}Asset object fields
| Field | Type | Description |
|---|---|---|
id | string | Unique asset identifier (UUID). |
video_id | string | The video this asset was detected in. |
space_id | string | The space (room) this asset belongs to. |
category | string | One of: appliance, fixture, furniture, structural, electronics, decor. |
subcategory | string | Specific type: "refrigerator", "fireplace", "rug", "door", "artwork", "staircase", etc. |
name | string | Human-readable name, e.g. "Area Rug", "Stainless Steel Refrigerator", "Paneled Interior Door". |
brand | string? | Detected brand name, if identifiable. |
model | string? | Detected model number, if visible. |
material | string? | Primary material: "stainless steel", "wood", "wool or synthetic", etc. |
color | string? | Detected color: "silver", "white", "red with traditional pattern", etc. |
dimensions | string? | Estimated dimensions if determinable. |
condition_rating | string? | One of: excellent, good, fair, poor. |
condition_notes | string? | Free-text description of condition details. |
estimated_value_usd | number? | AI-estimated replacement value in USD. Null if not determinable. |
confidence | string | AI confidence: high, medium, or low. |
detected_by | string | Which AI model produced the result: claude-haiku-4.5 or claude-sonnet-4.5. |
features | string[]? | Additional features if detected, e.g. "ice_maker", "self_cleaning". |
source | string | ai_detected, user_added, or user_edited. |
/v1/sessions/:idCancel a session and mark it as expired. Cannot be called on sessions that are already completed or expired.
Request
curl -X DELETE https://api.abodio.com/v1/sessions/f6414dd2-... \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx"Response 200 OK
{ "session_id": "f6414dd2-...", "status": "expired" }/v1/sessions/:id/upload-completeSignal that the video upload is finished and trigger AI processing. This is the server-side endpoint — call it from your backend after the user completes the upload.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
upload_token | string | Required | The upload token returned when the session was created. |
Request
curl -X POST https://api.abodio.com/v1/sessions/f6414dd2-.../upload-complete \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "upload_token": "44a0870c1b0adfdf..." }'Response 200 OK
{
"session_id": "f6414dd2-...",
"video_id": "9f867bc2-...",
"status": "processing",
"redirect_url": "https://yourapp.com/upload/success"
}Upload Client API
These endpoints are used by the hosted upload page and are authenticated via the upload token (not the API key). They're designed for browser-side use.
/v1/upload/:sessionId?token=...Fetch session data and TUS upload configuration for the frontend. Returns everything the browser needs to upload a video directly to storage.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Required | The upload token from session creation. |
Request
curl "https://api.abodio.com/v1/upload/f6414dd2-...?token=44a0870c..."Response 200 OK
{
"session_id": "f6414dd2-112b-43a8-9983-4570b29d195d",
"status": "created",
"constraints": {
"max_videos": 1,
"max_duration_sec": 300,
"max_file_size_bytes": 524288000,
"accepted_types": ["video/mp4", "video/quicktime", "video/webm", ...]
},
"upload": {
"tus_endpoint": "https://xxx.supabase.co/storage/v1/upload/resumable",
"auth_token": "eyJhbGciOiJIUzI1NiIs...",
"bucket": "videos",
"path": "f6414dd2-.../9f867bc2-.../video",
"video_id": "9f867bc2-7b0a-4ef0-8f70-837ce052c8df"
},
"redirect_urls": {
"success": "https://yourapp.com/upload/success",
"cancel": null,
"failure": null
},
"expires_at": "2026-02-12T20:50:39.839Z"
}Upload object fields
| Field | Type | Description |
|---|---|---|
tus_endpoint | string | Supabase Storage TUS endpoint for resumable uploads. |
auth_token | string | Bearer token for the TUS upload (Supabase anon key). |
bucket | string | Storage bucket name. |
path | string | Object path within the bucket. |
video_id | string | Pre-created video record UUID. |
/v1/upload/:sessionId/completeClient-side endpoint to signal the TUS upload is finished. This is the frontend equivalent of POST /v1/sessions/:id/upload-complete. Authenticated via upload token in the request body.
Request
curl -X POST "https://api.abodio.com/v1/upload/f6414dd2-.../complete" \
-H "Content-Type: application/json" \
-d '{ "upload_token": "44a0870c..." }'Asset Links API
Endpoints for generating verified reference links (manuals, support pages, parts, reviews) for a given asset.
/v1/asset-linksGenerates a curated list of verified links for a specific asset (manuals, support pages, parts, reviews). The endpoint forwards the request to a search-grounded AI model, verifies each returned URL resolves, and returns only real links in the categories you specified.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
asset_name | string | Required | Human-readable name of the asset, e.g. 'Samsung Refrigerator' |
brand_name | string | Required | Manufacturer brand, e.g. 'Samsung' |
model_number | string | Required | Model/serial identifier, e.g. 'RT30C3732S8/NL' |
category | string or string[] | Required | One or more link categories. Each returned link's type will be set to the best-matching value. |
country_prompt | string | Optional | Localization instruction. Defaults to 'USA'. Full sentences allowed, e.g. 'The user is located in United States. Use American English spelling.' |
todays_date | string | Optional | Human-readable or ISO date. Server-generated if omitted. |
Request
curl -X POST https://api.abodio.com/v1/asset-links \
-H "Authorization: Bearer abodio_sk_test_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"asset_name": "Samsung Refrigerator",
"brand_name": "Samsung",
"model_number": "RT30C3732S8/NL",
"category": ["manual", "parts", "reviews", "customer-support"],
"country_prompt": "USA",
"todays_date": "April 20, 2026"
}'Response 200 OK
{
"links": [
{
"name": "Samsung Refrigerator Support & Manuals",
"description": "Official Samsung support page with manuals and troubleshooting guides",
"url": "https://www.samsung.com/us/support/home-appliances/refrigerators/...",
"type": "customer-support"
},
{
"name": "Samsung Refrigerator Registration",
"description": "Register your Samsung refrigerator to activate warranty coverage",
"url": "https://www.samsung.com/us/support/register/product/refrigerator/...",
"type": "manual"
}
]
}| Field | Type | Description |
|---|---|---|
links[].name | string | Short descriptive title |
links[].description | string | One-sentence summary of the link's content |
links[].url | string | Verified URL (returns 2xx/3xx) |
links[].type | string | Category from the request — best match for this link |
Upload Flow Guide
There are two ways to handle video uploads:
Option A: Hosted upload page
The simplest integration. After creating a session, redirect the user to the hosted upload page. The page handles file selection, camera recording, TUS upload, and progress display.
https://video.abodio.com/upload/{session_id}?token={upload_token}When the upload completes, the user is redirected to your success_url.
Option B: Direct upload via TUS
For custom UIs. Use the TUS protocol to upload directly to Supabase Storage with resumable, chunked uploads.
- Call
GET /v1/upload/:sessionId?token=...to get TUS configuration. - Use
tus-js-client(or any TUS client) to upload the file with 6MB chunks. - Set the required TUS metadata:
bucketName,objectName,contentType. - On completion, call
POST /v1/upload/:sessionId/complete.
Option C: Simple PUT upload
For server-side uploads from your backend. Use the signed upload_url returned by session creation to PUT the file directly. Then call POST /v1/sessions/:id/upload-complete.
curl -X PUT "https://xxx.supabase.co/storage/v1/object/upload/sign/videos/..." \
-H "Content-Type: video/mp4" \
--data-binary @walkthrough.mp4Processing Pipeline
Once an upload is signalled complete, the video enters a multi-stage AI analysis pipeline. Each tier uses a different model optimized for its task.
Frame Extraction
The video is sent to a Python service running FFmpeg and PySceneDetect. It detects scene boundaries and extracts key frames at 1280x720 resolution.
Tier 1 — Full Inventory Scan
All frames are processed in batches of 15 by Claude Haiku 4.5. This fast vision model identifies all spaces, appliances, fixtures, furniture, structural features, electronics, and decor. Items with low confidence or in key categories (appliances, fixtures, electronics) are flagged for escalation.
Tier 2 — Detail Analysis
Flagged items are sent one frame at a time to Claude Haiku 4.5 for focused analysis. This tier identifies brands, models, materials, and provides more accurate condition assessments.
Tier 3 — Disagreement Resolution
When Tier 1 and Tier 2 disagree on an item's identity (brand, category, etc.), 1-3 frames are sent to Claude Sonnet 4.5 for a final determination with reasoning.
Deduplication & Storage
Items detected across multiple frames are clustered and merged. Final spaces and assets are stored in the database, and the session moves to completed.
AI Output Schema
Each tier in the processing pipeline produces structured JSON validated against a strict schema. The Tier 1 output is the primary schema — it generates the home summary, spaces, and items that ultimately become the spaces and assets you retrieve via the API.
Tier 1 output structure
The Tier 1 model receives batches of video frames and returns a complete inventory of the property. The output has three top-level objects:
home object
| Field | Type | Description |
|---|---|---|
property_type | string | One of: single_family, condo, apartment, townhouse, multi_family, mobile_home, other. |
property_subtype | string? | More specific type: "colonial", "ranch", "split-level", "studio", etc. |
overall_condition | string | One of: excellent, good, fair, poor. |
estimated_stories | integer? | Number of stories visible in the video. |
notable_features | string[] | High-level property features: "open floor plan", "hardwood throughout", etc. |
summary | string | One-sentence summary of the property. |
spaces array
| Field | Type | Description |
|---|---|---|
space_index | integer | Position in the spaces array (referenced by items). |
space_type | string | Room type: kitchen, bathroom, bedroom, living_room, dining_room, office, garage, basement, attic, laundry, hallway, closet, patio, balcony, entryway, mudroom, pantry, sunroom, bonus_room, utility, exterior, other. |
space_subtype | string? | Specific qualifier: "master_bedroom", "half_bath", "walk-in_closet". |
label | string | Human-friendly label: "Primary Kitchen", "Upstairs Bathroom". |
condition | string | Room condition: excellent, good, fair, poor. |
image_indices | integer[] | Which frame indices show this space. |
features | string[] | Notable room features: "granite countertops", "recessed lighting". |
flooring_type | string? | Floor material: "hardwood", "tile", "carpet". |
wall_finish | string? | Wall treatment: "painted drywall", "shiplap", "exposed brick". |
items array
| Field | Type | Description |
|---|---|---|
space_index | integer | References the space this item belongs to. |
category | string | One of: appliance, fixture, furniture, structural, electronics, decor. |
subcategory | string | Specific type: "refrigerator", "ceiling_fan", "sofa", "window". |
name | string | Display name: "French Door Refrigerator", "Pendant Light Fixture". |
brand | string? | Brand if identifiable. |
material | string? | Primary material. |
color | string? | Primary color. |
condition | string | Item condition: excellent, good, fair, poor. |
confidence | string | Detection confidence: high, medium, low. |
needs_review | boolean | Flagged for Tier 2 review (brand/model/condition uncertain). |
image_index | integer | Frame index showing this item best. |
Example Tier 1 output
{
"home": {
"property_type": "single_family",
"property_subtype": "colonial",
"overall_condition": "good",
"estimated_stories": 2,
"notable_features": [
"open floor plan",
"hardwood throughout",
"updated kitchen"
],
"summary": "Well-maintained two-story colonial with modern kitchen, hardwood floors, and spacious living areas."
},
"spaces": [
{
"space_index": 0,
"space_type": "kitchen",
"space_subtype": null,
"label": "Primary Kitchen",
"condition": "excellent",
"image_indices": [3, 4, 5],
"features": [
"granite countertops",
"stainless steel appliances",
"island with seating"
],
"flooring_type": "tile",
"wall_finish": "painted drywall"
},
{
"space_index": 1,
"space_type": "living_room",
"space_subtype": null,
"label": "Living Room",
"condition": "good",
"image_indices": [0, 1],
"features": [
"fireplace",
"crown molding",
"large windows"
],
"flooring_type": "hardwood",
"wall_finish": "painted drywall"
}
],
"items": [
{
"space_index": 0,
"category": "appliance",
"subcategory": "refrigerator",
"name": "French Door Refrigerator",
"brand": "Samsung",
"material": "stainless steel",
"color": "silver",
"condition": "excellent",
"confidence": "high",
"needs_review": false,
"image_index": 3
},
{
"space_index": 1,
"category": "structural",
"subcategory": "fireplace",
"name": "Gas Fireplace",
"brand": null,
"material": "stone",
"color": "gray",
"condition": "good",
"confidence": "high",
"needs_review": false,
"image_index": 0
}
]
}Tier 2 enrichment fields
Tier 2 receives flagged items for focused analysis. It adds or corrects the following fields:
| Field | Type | Description |
|---|---|---|
item_index | integer | Index of the item in the Tier 1 items array. |
brand | string? | Confirmed or corrected brand. |
model | string? | Identified model name/number. |
material | string? | Confirmed or corrected material. |
color | string? | Confirmed or corrected color. |
dimensions | string? | Approximate dimensions if discernible. |
condition | string | Confirmed or corrected condition. |
condition_notes | string? | Detailed condition notes: "minor scratches on door handle". |
estimated_value_usd | number? | Estimated replacement value in USD. |
confidence | string | Updated confidence after focused analysis. |
features | string[] | Additional features: "ice_maker", "self_cleaning". |
still_uncertain | boolean | True if item should escalate to Tier 3. |
Tier 3 resolution fields
Tier 3 makes a final determination when Tier 1 and Tier 2 disagree. It provides explicit reasoning for its decisions.
| Field | Type | Description |
|---|---|---|
item_index | integer | Index of the item in the Tier 1 items array. |
brand | string? | Final brand determination. |
model | string? | Final model determination. |
confidence | string | Final confidence after expert review. |
reasoning | string | Explanation of the decision: "Logo matches Samsung RF28 series based on handle style". |
final_category | string | Final category (may override Tier 1). |
final_subcategory | string | Final subcategory. |
final_name | string | Final display name. |
estimated_value_usd | number? | Final estimated value (may override Tier 2). |
bubble_version is provided at session creation, the Tier 1 schema dynamically validates categories and subcategories against your Bubble app's configured asset types. This ensures all detected items map to valid entries in your data model.Webhooks
If you provide a callback_url when creating a session, we'll send a POST request to that URL when the session reaches a terminal state.
Event types
| Event | Trigger |
|---|---|
session.completed | Processing finished successfully. Results are available. |
session.failed | Processing encountered an error. |
session.expired | Session timed out before the video was uploaded. |
session.completed payload
{
"id": "b3f7a2e1-9c84-4d56-a1b2-8e3f5d7c9a01",
"type": "session.completed",
"created_at": "2026-02-15T22:38:22.000Z",
"data": {
"session_id": "d7c05bfb-6eaa-4abb-8c64-d2e6e8e34c15",
"external_user_id": "user_2kFj9x",
"total_spaces": 12,
"total_assets": 49,
"processing_duration_seconds": 362
}
}session.failed payload
{
"id": "c4e8b3f2-1a95-4e67-b2c3-9f4a6e8d0b12",
"type": "session.failed",
"created_at": "2026-02-15T22:40:00.000Z",
"data": {
"session_id": "d7c05bfb-6eaa-4abb-8c64-d2e6e8e34c15",
"external_user_id": "user_2kFj9x",
"error_message": "Frame extraction failed: video codec not supported"
}
}Delivery & retries
Webhooks are delivered with up to 3 automatic retries using exponential backoff (1s, 2s, 4s delays). Each attempt has a 10-second timeout. Retries occur on 5xx responses and network errors. 4xx responses are treated as permanent failures and are not retried.
Webhook payload fields
| Field | Type | Description |
|---|---|---|
id | string | Unique event identifier. |
type | string | Event type (see table above). |
created_at | string | ISO 8601 timestamp. |
data.session_id | string | The session this event belongs to. |
data.external_user_id | string | Your user identifier. |
data.total_spaces | number | Number of rooms/spaces detected (completed only). |
data.total_assets | number | Number of assets detected (completed only). |
data.processing_duration_seconds | number | Total processing time (completed only). |
data.error_message | string | Error description (failed only). |
Error Reference
All errors follow a consistent format:
{
"error": {
"code": "INVALID_SESSION",
"message": "Session not found or invalid token"
}
}| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | MISSING_TOKEN | Upload token not provided in the request. |
| 400 | FILE_NOT_FOUND | No file found in storage. Upload the video before signalling completion. |
| 401 | MISSING_API_KEY | No API key in the Authorization header. |
| 401 | INVALID_API_KEY | API key is invalid or has been revoked. |
| 404 | NOT_FOUND | The requested session does not exist. |
| 404 | INVALID_SESSION | Session not found or the upload token doesn't match. |
| 404 | NO_VIDEO | No pending video record found for this session. |
| 409 | INVALID_STATUS | Session is not in the expected state for this operation. |
| 410 | SESSION_EXPIRED | Session has passed its expiration time. |
| 500 | SESSION_CREATE_FAILED | Database error while creating the session. |
| 500 | VIDEO_CREATE_FAILED | Database error while creating the video record. |
| 409 | ALREADY_PROCESSING | Session upload was already completed and processing has started. |
| 500 | PROCESSING_FAILED | Failed to start the video processing workflow. |
| 429 | Rate limit exceeded | Too many requests. Check Retry-After header for seconds to wait. See rate limit headers below. |
| 503 | BUBBLE_ENUMS_INVALID | Could not fetch or validate Bubble enums for the provided bubble_version. The Bubble API may be unavailable or returned empty data. |
Constraints & Limits
Video requirements
| Constraint | Value |
|---|---|
| Maximum file size | 500 MB |
| Maximum duration | 600 seconds (10 minutes) |
| Accepted formats | video/mp4, video/quicktime, video/webm, video/x-msvideo, video/x-matroska |
| Frame extraction resolution | 1280 x 720 px |
| Frames per scene | 2 |
Session limits
| Constraint | Value |
|---|---|
| Default session TTL | 24 hours |
| Maximum videos per session | Configurable (default: 1) |
| Upload token length | 64 hex characters |
| Metadata field | Arbitrary JSON object (max 10 KB serialized) |
| external_user_id max length | 255 characters |
| URL fields | Must be public HTTP(S) endpoints (private/internal IPs rejected) |
| bubble_version format | Alphanumeric with hyphens/underscores, max 50 characters |
Rate limits
| Constraint | Value |
|---|---|
| Write endpoints (POST, DELETE) | 60 requests per minute per API key |
| Read endpoints (GET) | 120 requests per minute per API key |
Rate-limited responses return 429 Too Many Requests with the following headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window. |
X-RateLimit-Remaining | Requests remaining in the current window. |
Retry-After | Seconds to wait before retrying (included on 429 responses). |
AI processing tiers
| Tier | Model | Batch Size | Purpose |
|---|---|---|---|
| Tier 1 | Claude Haiku 4.5 | 15 frames | Full inventory scan |
| Tier 2 | Claude Haiku 4.5 | 1 frame | Detail analysis (brand, model) |
| Tier 3 | Claude Sonnet 4.5 | 1-3 frames | Disagreement resolution |
Escalation criteria
Items are escalated from Tier 1 to Tier 2 when:
- Confidence is
low - Category is
appliance,fixture, orelectronics(brand/model identification important) - Brand is
nulldespite being in a brandable category
Items are escalated from Tier 2 to Tier 3 when Tier 1 and Tier 2 disagree on the item's identity (category, brand, or name).