Spaces:
Configuration error
Scene Graph Manager API Usage
This document gives a concise, high-signal overview of the HTTP API so other services can integrate without reading the whole codebase.
Refresh Checklist (for future updates)
Use this prompt when endpoints change:
Run
rg "@app" -n api/app.pyand list new/modified routes. Updatedocs/api_usage.mdwith:
- Endpoint table (method, path, summary)
- Auth / header requirements
- Request & response JSON samples (curl when useful)
- Notes on query params, error behaviors, and streaming endpoints. Re-run
python -m compileall api/app.pyand ensure the doc still reflects reality.
Base URL
All paths below are relative to:
https://scene-graph-mgr-api.fly.dev
Authentication
- Public endpoints are unauthenticated but subject to Fly.io rate limits.
- Internal write endpoints require a shared secret header:
x-internal-secret: <secret>. - Standard error payload:
{
"detail": "Human readable message",
"error_code": "optional-machine-code"
}
4xx indicates caller issues (validation, missing scene). 5xx means server-side failure.
Endpoint Map
| Method | Path | Summary |
|---|---|---|
| GET | /healthz |
Liveness check. |
| GET | /scenes |
List scenes with summary metadata (optional include_empty). |
| PUT | /scenes/{scene_id} |
Overwrite a scene with a full graph payload. |
| PATCH | /scenes/{scene_id} |
Apply an RFC 6902 patch to the latest graph. |
| POST | /scenes/{scene_id}/create |
Seed an empty scene (optionally overwrite). |
| GET | /scenes/{scene_id}/versions/latest |
Latest graph version. |
| GET | /scenes/{scene_id}/versions |
Metadata for all versions. |
| GET | /scenes/{scene_id}/versions/{version_id} |
Raw version record (JSON). |
| POST | /scenes/{scene_id}/add_image |
Upload image bytes and enqueue processing. |
| POST | /scenes/{scene_id}/add_images_from_keys |
Enqueue processing for existing S3 keys. |
| POST | /scenes/{scene_id}/upload_video |
Upload video and queue frame extraction batch. |
| POST | /scenes/{scene_id}/upload_image |
Browser upload → WebP resize → presigned URL. |
| GET | /scenes/{scene_id}/instances |
List stored instances for the scene (status filters optional). |
| GET | /scenes/{scene_id}/objects/{obj_id}/beliefs |
Latest improver belief for an object. |
| GET | /scenes/{scene_id}/objects/{obj_id}/instances |
List stored instances for an object (status filters optional). |
| GET | /scenes/{scene_id}/improver/backlog |
Inspect improver queue/backlog for a scene. |
| POST | /scenes/{scene_id}/objects/{obj_id}/instances/{instance_id}/improve_instance |
Queue improver workflow for an instance. |
| GET | /scenes/{scene_id}/runlogs |
Recent improver run logs (filterable). |
| GET | /scenes/{scene_id}/change-requests |
Inspect change request queue (pending/applied). |
| GET | /scenes/{scene_id}/change-summaries |
Stream applied change summaries (newest first). |
| GET | /scenes/{scene_id}/diff |
Structural diff between two versions (machine patch & stats). |
| GET | /scenes/{scene_id}/diff-semantic |
Semantic diff (ID-aware ops). |
| GET | /scenes/{scene_id}/images/presign |
Generate presigned GET URL for an image key. |
| GET | /scenes/{scene_id}/media |
List recorded media assets (images/videos). |
| POST | /scenes/{scene_id}/media |
Upsert scene media entries supplied by workers or services. |
| GET | /jobs/{job_id} |
Inspect queued/finished RQ jobs. |
| POST | /stream3r/jobs |
Submit a reconstruction job (pose pointmap or model build). |
| GET | /stream3r/jobs/{job_id} |
Inspect a Stream3R job record. |
| GET | /stream3r/jobs |
List Stream3R jobs (filter by scene, type, status). |
| GET | /stream3r/jobs/{job_id}/events |
Server-Sent Events feed for job lifecycle updates. |
| GET | /stream3r/models/{scene_id}/presign |
Presign the latest model_build scene.glb for download. |
| POST | /internal/commit-version |
Worker/internal scene commit (requires secret). |
| GET | /debug/s3-ping |
Smoke-test S3/B2 credentials. |
| WS | `/ws?channel=all | {scene_id}` |
Endpoint Details & Examples
Health & Diagnostics
GET /healthz
Returns { "ok": true, "ts": "ISO timestamp" } for uptime monitoring.
GET /debug/s3-ping
Verifies object storage connectivity using a put/delete round-trip. Good for smoke tests.
Scene Lifecycle
GET /scenes
List scenes. Optional include_empty=true includes scenes with no versions yet.
curl "https://scene-graph-mgr-api.fly.dev/scenes?include_empty=true"
Response contains summaries with counts and most recent version metadata.
PUT /scenes/{scene_id}
Replace entire scene graph.
Request body (SceneGraphEnvelope):
{
"scene_location_id": "scene-123",
"scene_graph": {"objects": [], "relations": []},
"meta": {"source": "manual"}
}
Creates a new version record and broadcasts scene.put on WebSocket.
PATCH /scenes/{scene_id}
Apply RFC 6902 patch to latest graph. Optional base_version guard prevents lost updates.
{
"scene_location_id": "scene-123",
"json_patch": [
{"op": "add", "path": "/objects/-", "value": {"id": "chair-1", "attributes": {}}}
],
"base_version": 1728400000000
}
POST /scenes/{scene_id}/create
Seeds an empty graph. overwrite=true allows reseeding existing scenes.
Version Reads
GET /scenes/{scene_id}/versions/latest→ latest full graph.GET /scenes/{scene_id}/versions→ list of{version_id, created_at, bytes}.GET /scenes/{scene_id}/versions/{version_id}→ raw record (graph + metadata).
Image Upload & Processing
POST /scenes/{scene_id}/add_image
Multipart upload (file=@image.jpg). Stores bytes via S3-compatible API, seeds scene if empty, enqueues worker.tasks.process_image_for_scene. Response includes RQ job_id and filename.
POST /scenes/{scene_id}/add_images_from_keys
JSON body:
{
"keys": ["scenes/scene-123/images/20241008/a.png"],
"room_hint": "living_room",
"prompt": "describe objects",
"bounding_boxes": {"a.png": [[0.1,0.2,0.5,0.8]]}
}
Seeds scene if needed and enqueues batch worker job.
Response (AddImagesFromKeysResponse):
{
"scene_location_id": "scene-123",
"queued_at": "2024-10-08T14:12:05Z",
"job_id": "rq-job-id",
"keys": [
"scenes/scene-123/images/20241008/a.png",
"scenes/scene-123/images/20241008/b.png"
]
}
POST /scenes/{scene_id}/upload_image
For browser uploads. Accepts multipart file, resizes to WebP (max width 1024), uploads, and returns:
{
"scene_location_id": "scene-123",
"key": "scenes/scene-123/images/20241008/abc.webp",
"url": "https://...presigned...",
"width": 768,
"height": 512,
"bytes": 123456
}
GET /scenes/{scene_id}/images/presign
Query parameters: key (must reside under scenes/{scene_id}/images/… or scenes/{scene_id}/videos/…) and optional expires. Returns { "url": "...", "expires_in": 900 }.
Video Upload & Frame Extraction
POST /scenes/{scene_id}/upload_video
Accepts a multipart video upload (e.g., file=@walkthrough.mp4). Stores the binary in object storage and seeds an empty scene when needed. The worker enqueues process_video_for_scene, which:
- extracts WebP frames at 2fps by default (
frame_interval=0.5seconds) and stores them underscenes/{scene_id}/images/video_frames/...(or keeps the source underscenes/{scene_id}/videos/...) so the/images/presignendpoint can serve them; - retries with a software H.264 transcode if AV1 or other codecs fail to decode on the host;
- publishes the same
scene.updateevent stream as still-image uploads.
Response payload (UploadVideoResponse):
{
"scene_location_id": "scene-123",
"key": "scenes/scene-123/videos/20241008/abcd1234.mp4",
"filename": "walkthrough.mp4",
"size_bytes": 456789012,
"content_type": "video/mp4",
"queued_at": "2024-10-08T14:12:05Z",
"job_id": "rq-job-id"
}
Clients should poll GET /jobs/{job_id} to track progress. When job.result.frame_keys is present the frame extraction succeeded.
Scene Media
GET /scenes/{scene_id}/media
Lists stored media records for a scene. Use media_type=image to filter to keyframes vs. media_type=video for source clips.
curl "https://scene-graph-mgr-api.fly.dev/scenes/scene-123/media?media_type=image&limit=50"
Response (SceneMediaListResponse) includes the total slice returned plus ISO timestamps normalized to UTC.
POST /scenes/{scene_id}/media
Upserts media entries (usually called by workers after uploading files). Payload:
{
"entries": [
{
"file": "scenes/scene-123/keyframes/frame_000012.jpg",
"media_type": "image",
"captured_at": "2024-10-08T14:12:05Z"
},
{
"file": "scenes/scene-123/videos/20241008/abcd1234.mp4",
"media_type": "video"
}
]
}
media_typeis optional—when omitted the server infers it from the filename (imagevsvideo).captured_ataccepts ISO-8601 strings (UTC preferred). If omitted, the server storesnow().- Existing rows are updated in-place thanks to an upsert on the
filecolumn.
Response (SceneMediaBatchResponse) summarizes how many entries were accepted:
{
"scene_id": "scene-123",
"accepted": 2,
"skipped": 0,
"files": [
"scenes/scene-123/keyframes/frame_000012.jpg",
"scenes/scene-123/videos/20241008/abcd1234.mp4"
]
}
Improver Monitoring
GET /scenes/{scene_id}/improver/backlog
Summarizes outstanding improver work for a scene. Default statuses are pending, queued, and processing, plus records that do not yet have a status. Use this to detect when the improver has drained the backlog.
Query parameters:
limit(default200, max2000) — cap the number of instances returned.status— optional list to override which statuses count as “not yet processed.” Omit to use the defaults.include_missing_status(defaulttrue) — include rows withstatus IS NULL.
Response:
{
"scene_id": "scene-123",
"count": 2,
"instances": [
{
"scene_id": "scene-123",
"obj_id": "obj-1",
"instance_id": "inst-42",
"status": "pending",
"status_reason": "ambient",
"status_changed_at": "2024-10-08T14:15:06Z",
"last_event_at": "2024-10-08T14:15:06Z",
"created_at": "2024-10-08T13:59:40Z",
"data": {
"image_id": "img-998",
"bbox_xyxy": [0.1, 0.2, 0.5, 0.8]
}
}
]
}
When count is zero the improver has no pending work for that scene.
Improver & Beliefs
POST /scenes/{scene_id}/objects/{obj_id}/instances/{instance_id}/improve_instance
Queues improver workflow. Body adheres to InstanceEvent schema. Example:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"scene_id": "scene-123",
"obj_id": "sofa-1",
"instance_id": "inst-456",
"image_id": "scenes/scene-123/images/20241008/sofa.png",
"bbox_xyxy": [0.1, 0.3, 0.6, 0.9]
}' \
https://scene-graph-mgr-api.fly.dev/scenes/scene-123/objects/sofa-1/instances/inst-456/improve_instance
Response: { "enqueued": true, "job_id": "rq-job-id" }.
The worker embeds, seeds Qdrant, and calls scene_improver.tasks.improve_scene.
GET /scenes/{scene_id}/objects/{obj_id}/beliefs
Returns latest belief payload (name Dirichlet, attribute betas, relations) or 404 if none stored.
GET /scenes/{scene_id}/instances
Returns persisted instances across every object in the scene. Query params:
limit(1–5000, default 1000)status(optional, repeatable) — only include instances whosestatusmatches one of the provided values.exclude_status(optional, repeatable) — omit instances whosestatusmatches any of the provided values (e.g.,exclude_status=supersededto skip reassigned instances).
Response mirrors the storage map:
{
"scene_id": "scene-123",
"count": 5,
"objects": {
"sofa-1": [{...}, {...}],
"lamp-4": [{...}]
}
}
GET /scenes/{scene_id}/objects/{obj_id}/instances
Returns the persisted instance rows for the object. Query params:
limit(1–1000, default 100)status(optional, repeatable) — only include instances whosestatusmatches one of the provided values.exclude_status(optional, repeatable) — omit instances whosestatusmatches any of the provided values.
Example request skipping superseded items:
curl "https://scene-graph-mgr-api.fly.dev/scenes/scene-123/objects/sofa-1/instances?limit=20&exclude_status=superseded"
Response:
{
"scene_id": "scene-123",
"obj_id": "sofa-1",
"count": 2,
"instances": [
{
"id": "inst-456",
"image_id": "scenes/...",
"bbox_xyxy": [0.1,0.3,0.6,0.9],
"captured_at": 1728401000,
"status": "processed"
},
{
"id": "inst-123",
"image_id": "scenes/...",
"bbox_xyxy": [0.05,0.2,0.4,0.7],
"status": "pending"
}
]
}
GET /scenes/{scene_id}/runlogs
Query params:
limit(1–1000, default 100)obj_id(optional filter)instance_id(optional filter)
Response contains records newest-first with step, message, data, timestamps, and run IDs.
GET /scenes/{scene_id}/change-requests
Inspect the change request queue. Query params:
state(optional:pending,applied,stale)limit(1–200, default 50)
Returns an array mirroring the DB record with preconditions, payload, result, and the new applied_summary field:
[
{
"request_id": "95e8...",
"scene_id": "scene-123",
"obj_id": "table-1",
"requested_by": "belief-agent",
"state": "applied",
"confidence": 0.92,
"payload": {"operations": [...]},
"result": {"summary": "Renamed object \"wooden table\" from \"table\" to \"dining table\""},
"applied_at": "2024-11-19T20:14:32.123Z",
"applied_summary": "Renamed object \"wooden table\" from \"table\" to \"dining table\""
}
]
GET /scenes/{scene_id}/change-summaries
Lightweight feed of applied change blurbs, ordered by applied_at descending. Supports limit (1–200, default 50).
curl "https://scene-graph-mgr-api.fly.dev/scenes/scene-123/change-summaries?limit=20"
[
{
"request_id": "95e8...",
"scene_id": "scene-123",
"obj_id": "table-1",
"applied_version": 1729300042000,
"applied_at": "2024-11-19T20:14:32.123Z",
"summary": "Updated object \"wooden table\" attribute \"size\" from \"medium\" to \"large\""
},
{
"request_id": "73f1...",
"scene_id": "scene-123",
"summary": "Renamed object \"wooden table\" from \"table\" to \"dining table\""
}
]
Use this endpoint for ambient “stream of consciousness” UIs showing how the improver evolves the scene.
Diffs
GET /scenes/{scene_id}/diff
Compare two versions (from_version, to_version). Optional mode query (patch, summary, both). Returns machine patch plus stats when requested.
GET /scenes/{scene_id}/diff-semantic
ID-aware diff returning ordered operations (append/remove/replace) with summary stats.
Stream3R Reconstruction Jobs
The Stream3R API wraps the reconstruction workers (pose pointmaps and full models) and provides idempotent enqueue, polling, and event streaming.
POST /stream3r/jobs
Submit a reconstruction job. Supported job_type values are pose_pointmap and model_build. Provide at least one frame (url or path) and optional client_request_id for idempotency (subsequent calls reuse the existing job and return 200 OK).
curl -X POST https://scene-graph-mgr-api.fly.dev/stream3r/jobs \
-H "Content-Type: application/json" \
-d '{
"job_type": "pose_pointmap",
"scene_id": "scene-123",
"frames": [
{"url": "https://cdn.example/scene-123/frame_0000.webp"},
{"path": "/mnt/captures/scene-123/frame_0001.png"}
],
"session_settings": {"prediction_mode": "pointmap"},
"client_request_id": "scene-123-20241008-run1"
}'
Response (202 Accepted on first submission):
{
"job_id": "d8f8a3fc-3aed-441c-ac78-2b953a9229bf",
"job_type": "pose_pointmap",
"scene_id": "scene-123",
"status": "queued",
"created_at": "2024-10-08T14:12:05.417Z",
"payload": {
"job_type": "pose_pointmap",
"scene_id": "scene-123",
"frames": [
{"url": "https://cdn.example/scene-123/frame_0000.webp"},
{"path": "/mnt/captures/scene-123/frame_0001.png"}
],
"session_settings": {"prediction_mode": "pointmap"}
}
}
GET /stream3r/jobs/{job_id}
Fetch the canonical job record (backed by Postgres). Fields include:
status:queued,started,progress,finished, orfailedresult: worker-published artifact manifest (S3 URLs, local paths)error: error string when status isfailed- timestamps (
created_at,started_at,completed_at)
Typical successful response:
{
"job_id": "d8f8a3fc-3aed-441c-ac78-2b953a9229bf",
"job_type": "model_build",
"scene_id": "scene-123",
"status": "finished",
"created_at": "2024-10-08T14:12:05.417Z",
"started_at": "2024-10-08T14:12:12.998Z",
"completed_at": "2024-10-08T14:24:39.221Z",
"result": {
"model_dir": "s3://bucket/scene-123/stream3r/models/20241008",
"summary_url": "s3://bucket/scene-123/stream3r/models/20241008/summary.json"
},
"error": null,
"client_request_id": "scene-123-20241008-run1"
}
GET /stream3r/jobs
List jobs with optional filters:
scene_idjob_typestatuslimit(1–200, default 50)offset(default 0)
Returns { "jobs": [...], "limit": 50, "offset": 0 } with the same schema as the single-job response.
GET /stream3r/jobs/{job_id}/events
Server-Sent Events feed backed by Redis Streams. Use it for near-real-time updates in browser dashboards.
curl --no-buffer \
-H "Accept: text/event-stream" \
"https://scene-graph-mgr-api.fly.dev/stream3r/jobs/d8f8a3fc-3aed-441c-ac78-2b953a9229bf/events"
Events are emitted as standard SSE payloads:
id: 1728409930123-0
event: progress
data: {"job_id":"d8f8a3fc-3aed-441c-ac78-2b953a9229bf","status":"progress","progress":65}
id: 1728409960456-0
event: finished
data: {"job_id":"d8f8a3fc-3aed-441c-ac78-2b953a9229bf","status":"finished","result_url":"s3://bucket/.../summary.json"}
Reconnect with the last id to resume (?last_id=<redis-stream-id>). When the worker encounters an error the stream emits event: failed with an error field.
Implementation note: the current worker stub still returns
status="failed"witherror="stream3r worker not implemented"until the GPU-backed handler ships. Downstream clients should surface the error text to operators and may retry later.
GET /stream3r/models/{scene_id}/presign
Fetch a presigned download URL for the most recent model_build job's scene.glb. Optional query params:
job_id— force a specific job (must belong to the scene).expires— TTL in seconds (default 900, range 60–86400).
curl "https://scene-graph-mgr-api.fly.dev/stream3r/models/scene-123/presign?expires=600"
Response:
{
"scene_id": "scene-123",
"job_id": "d8f8a3fc-3aed-441c-ac78-2b953a9229bf",
"key": "scenes/scene-123/stream3r/models/20241008/scene.glb",
"url": "https://s3.amazonaws.com/...signature...",
"expires_in": 600
}
Returns 404 if no successful model build exists or the job did not publish a GLB artifact.
Jobs & Internal Ops
GET /jobs/{job_id}
Inspect RQ job status. Returns timestamps, result payload (if finished), and truncated stack trace when failed.
POST /internal/commit-version
Worker-only commit. Body:
{
"scene_location_id": "scene-123",
"scene_graph": {...},
"base_version": 1728400000000,
"meta": {"source": "worker"}
}
Requires x-internal-secret header when enabled. Creates new version, broadcasts scene.update, and publishes on Redis pub/sub.
WebSocket Stream
WS /ws?channel=all|{scene_id}
Receives JSON events when scenes change (scene.create, scene.put, scene.patch, scene.update). Use to refresh UI state in real time.
Notes & Best Practices
- All timestamps are UTC ISO strings.
- Scene writes (
PUT,PATCH,create,commit-version) broadcast on WebSocket and publish to Redis channelscene_events. - Object storage paths follow
scenes/{scene_id}/images/...; presign endpoint enforces this prefix. - Scene graph payloads no longer embed instance blobs; use the
/instancesendpoint for that data. - Postgres storage is required; filesystem fallbacks have been removed from the API/worker flows.
- Queue jobs default to 15-minute timeout (image) or 30-minute (batch). Track job progress via
/jobs/{job_id}or Redis CLI. - Improver run logs are persisted to Postgres (if configured) and mirrored to JSONL under
SCENE_RUN_LOG_DIR.