This reference is for integrators and contributors. It covers two HTTP surfaces:
- The mem service: a Python FastAPI service under
services/mem, responsible for memory storage, retrieval, and ReID vector management. - The dashboard / host HTTP API: the unified TypeScript HTTP server started by
npx @reflectworld/reflectworld start, defaulting tohttp://localhost:3210. It serves the UI, tool calls, source management, memory proxying, media, and WebSockets.
This page documents the current interface contract. Mutation, proxy, chat, and webhook endpoints require a session or token in multi-user mode; single-user local mode remains open by default.
Type notation: ? means optional. string, number, boolean, object, and array are JSON types.
The Dashboard also uses realtime WebSocket events internally. Those events are not treated as a stable public integration API in the first open-source release; prefer the HTTP endpoints unless you are contributing to the Dashboard itself.
- Process: Python FastAPI, source in
services/mem/src/main.py; request models inservices/mem/src/models.py. - Default listen address:
127.0.0.1:8219, with the port injected by the host throughREFLECTWORLD_MEM_PORT. - Authentication assumption: the mem service binds to loopback and is called internally by the TypeScript host over
127.0.0.1. Public or multi-machine deployments should add shared-token middleware. Current source has no auth middleware, so this document assumes deployment on a trusted local network. - Multimodal memory model: 9 Qdrant collections organized by time scale and abstraction level:
episodic_trace,episodic_entity,episodic_schema,episodic_visual,semantic_entity,semantic_context,procedural, plus ReID collectionsreid_entityandreid_face. - Scope identifiers: most reads and writes filter by
user_id, the memory isolation key, with optionalcamera_id,entity_id,video_id, and similar identifiers.
Writes memory from video analysis, entity data, or raw messages. Episodic trace/entity routing is inferred from the data.
Request body, MemoryCreateRequest:
| Field | Type | Description |
|---|---|---|
video_data |
object? | Structured video analysis payload, including summary and targets. |
entity_data |
array? | Per-entity data that drives episodic and semantic memory generation. |
messages |
any? | Raw input: string, object, or list of objects. |
user_id |
string? | User scope, the primary memory isolation key. |
agent_id / run_id / video_id / camera_id |
string? | Additional scope identifiers. |
metadata |
object? | Additional metadata. |
infer |
boolean? | Whether to run LLM inference on the input. Defaults to true. |
memory_type |
string? | semantic_entity or procedural. Trace/entity are inferred from data; schema/context use dedicated endpoints. |
prompt |
string? | Custom LLM prompt. |
mode |
string? | Capture mode: snapshot, event, or stream. |
event_id |
string? | Event id when mode='event'. |
Perception writes include timeline metadata inside metadata:
media_start_msandmedia_duration_mscontain the numeric source interval;source_time_labelis its human-readable interval;time_basisexplains how to interpret the interval, normallyvideo_relative,wall_clock, or a dataset-specific basis such asdataset_day_time.
Older or direct API writes may omit the two source-time fields. Readers must
continue to use media_start_ms as the fallback rather than assuming wall-clock
time.
Response: { results: [{ id, collection, memory, event, entity_id?, importance? }, ...] }. Semantic and procedural writes trigger the memory:memory_added Monitor webhook.
Errors: memory_type equal to episodic_schema or semantic_context returns 400 and must use the dedicated endpoint. ValueError maps to 400; other exceptions map to 500.
Lists stored memories by identifiers.
Query parameters: user_id, agent_id, run_id, video_id, entity_id, camera_id, memory_type; limit defaults to 100; sort_order is asc for oldest first or desc for newest first. At least one identifier is required, otherwise the service returns 400.
Response: a list of memory objects. Shape depends on memory_type.
Path parameter: memory_id.
Query parameters: memory_type?, return_all_matches? defaulting to false.
Response: one memory object. Missing memory returns 404.
Response: change history for the memory. Missing history returns 404.
Path parameter: memory_id. Query parameter: memory_type?.
Request body, UpdateMemoryRequest: data is the new content or base64 embedding; data_type? defaults to "txt".
Response: update result. ValueError maps to 404.
Path parameter: memory_id. Query parameters: memory_type?, data_type? defaulting to "txt".
Response: delete result. ValueError maps to 404.
Query parameters: user_id, agent_id, run_id, video_id, entity_id, camera_id, memory_type, data_type. At least one identifier is required, otherwise the service returns 400.
Response: delete result.
Request body, SearchRequest:
| Field | Type | Description |
|---|---|---|
query |
string | Required search text or base64-encoded embedding. |
user_id / agent_id / run_id / entity_id / camera_id / video_id |
string? | Filter identifiers. |
mode |
string? | Capture mode filter: snapshot, event, or stream. |
memory_type |
string? | Collection name or group alias, such as episodic, semantic, or all. |
limit |
number? | Maximum result count. Defaults to 100. |
threshold |
number? | Minimum similarity score. |
filters |
object? | Additional key-value filters passed to the vector store. |
Response: matching memory list with similarity scores. ValueError maps to 400.
Aggregates multiple events into one schema-level summary.
Request body, AddSchemaRequest: summary string required, event_ids array of strings required, time_range object with start_ms and end_ms required, camera_id string required, and user_id? defaulting to "default".
Response: creation result.
Creates or updates Semantic Context for one camera, including scene, known entities, routine, and anomaly baseline.
Request body, UpsertContextRequest:
| Field | Type | Description |
|---|---|---|
camera_id |
string | Required camera being described. |
scene_description |
string | Required scene description. |
known_entities |
string | Required known entities. |
daily_routine |
string | Required daily routine. |
anomaly_baseline |
string | Required anomaly baseline. |
user_notes |
string? | User notes preserved across LLM profile updates. |
user_id |
string? | Defaults to "default". |
last_llm_update |
string? | ISO timestamp of the last LLM update, used for cold-start recovery. |
last_update_segment_count |
number? | Segment count at the last LLM update. |
last_agent_consult |
string? | ISO timestamp of the last agent consultation. |
last_consult_segment_count |
number? | Segment count at the last consultation. |
consecutive_no_updates |
number? | Consecutive LLM cycles without updates. |
Response: upsert result.
Creates or updates an identity memory with category=identity and importance=1.0. Identity memories are protected from LLM merge rewrites.
Request body, SetIdentityRequest: entity_id required, for example person_5; identity required, for example Neighbor Zhang, familiar; status? defaults to familiar and may be familiar, unfamiliar, or admin; user_id?; camera_id?.
Response: creation or update result.
Marks episodic memory media_urls for a segment as expired by setting them to null. This supports graceful degradation after media retention expires.
Request body, ExpireMediaRequest: segment_id required, equal to the video id; camera_id? narrows the filter.
Response: expiration result.
ReID is an evidence system. It stores detection-produced face and body embedding vectors, but does not make identity decisions. Collections are limited to reid_face and reid_entity; other values return 400. subject_index is assigned and persisted by the host TypeScript side. The mem service does not allocate it and only echoes the value passed by TS. See the architecture overview.
Request body, ReidSearchRequest: vector array of floats required, collection required as reid_entity or reid_face, limit? defaulting to 10, threshold? as minimum cosine similarity, user_id?, and include_history? defaulting to false. When include_history is true, the top hit includes semantic and episodic memories for the matched entity.
Response: { results: [{ entity_id, score, ...payload }] }.
Request body, ReidUpsertRequest:
| Field | Type | Description |
|---|---|---|
entity_id |
string | Required entity identifier. |
vector |
array[float] | Required feature vector. |
collection |
string | Required, reid_entity or reid_face. |
quality |
number | Detection quality score. Defaults to 0.5. Higher is more reliable. |
yaw / pitch |
number? | Face yaw and pitch stored in slot 0 payload. |
user_id |
string? | User that observed the feature. |
camera_id |
string? | Observation source; only used for cleanup, not default retrieval filtering. |
category |
string? | Subject category for display numbering. |
subject_index |
number? | Stable display number assigned by the host runtime. |
name |
string? | Resolved real-world identity name. |
first_seen |
any? | Host-assigned first-seen timestamp. |
metadata |
object? | Additional metadata. |
Response: upsert result.
Writes multiple embedding slots in one request. Face vectors are written first, then body vectors. Partial success, such as face success and body failure, still returns 200 with body_slots_written:0.
Request body, ReidBatchInitRequest: entity_id required; face_vectors? array of {vector, quality?, yaw?, pitch?, body_yaw_ratio?, visibility?}; body_vectors? with the same shape; user_id?; camera_id?; category?; subject_index?; name?; first_seen?. At least one of face_vectors or body_vectors must be non-empty, otherwise the service returns 400.
Response: batch write result with slot counts.
Adds a new viewpoint for a matched entity, refreshes a higher-quality version, or skips when the existing version is better.
Request body, ReidGalleryUpdateRequest: entity_id required, vector required, collection required, quality? defaulting to 0.5, yaw?, pitch?, body_yaw_ratio? where >0.7 is front, 0.15-0.7 is side, and <0.15 is back, visibility? such as full, upper, or partial, user_id?, camera_id?.
Response: update result indicating add, refresh, or skip.
Writes a resolved real identity name into every gallery slot for an entity.
Request body, ReidUpdateIdentityRequest:
| Field | Type | Description |
|---|---|---|
entity_id |
string | Required ReID entity identifier. |
name |
string | Required resolved real identity name. |
user_id |
string? | User scope. |
subject_index |
number? | Stable display number assigned by host-side TypeScript. It is echoed to all gallery slots together with name, keeping the name rewrite and numbering snapshot synchronized. |
Response: update result including affected slot count.
Used for source lifecycle cleanup. ReID retrieval is scoped by user_id, not by camera_id.
Query parameters: camera_id required, user_id?.
Response: delete result.
Deletes and recreates reid_face and reid_entity. It does not touch episodic, semantic, or procedural memory.
Response: reset result.
Clears every stored memory collection.
Response: { "message": "All memories reset successfully" }.
Expected auth: dangerous operation. Public or multi-machine deployment should require an admin token; trusted local mode remains open.
Re-runs collection creation for the current prefix. The operation is idempotent and does not modify existing collections. It is used when the managed mem process is alive but collections were externally removed, for example by E2E cleanup.
Response: { status, prefix, collections: [...] }.
Response: { status:"healthy", version, service, collection_prefix, memory_types:[...], reid_collections:[...], vector_store:"qdrant", llm_provider, embedder_provider }. This endpoint is not part of the OpenAPI schema.
Response: the same fields as /health, plus initialized boolean. When not initialized, status is "not_initialized".
- Process topology:
npx @reflectworld/reflectworld startcreates one Node.js HTTP server inpackages/reflectworld/src/cli/start.ts, defaulting tohttp://localhost:3210.- The top-level server directly handles
/api/health,/api/tools, and/api/tool/:name. - All other paths are delegated to
DashboardServerinpackages/reflectworld/src/services/dashboard-server.tsthrough itsregisterHandlercallback. This includes UI, source management, memory proxying, media, and WebSockets.
- The top-level server directly handles
- CORS: both top-level and dashboard paths set
Access-Control-Allow-Origin: *for local usability. Public deployments should tighten this. - Auth model: the dashboard server has a dashboard session mechanism using
session_idanduser_id, passed by cookie orx-dashboard-session-id/x-dashboard-user-idheader, query, or body.- Single-user local mode, with
sessionConfig.enabled=false, is the default.requireDashboardSessionallows requests without a session for the trusted local experience. - Multi-user mode, used by public demos or shared deployments, requires a valid session for mutation, proxy, and chat endpoints.
user_idbecomes the memory isolation key, and sources/memories are visible by session scope.
- Single-user local mode, with
- Auth column notation:
publicmeans no session is required.session in multi-usermeans a session is required in multi-user mode and single-user mode remains open.loopback-onlymeans only local loopback callers are accepted.
| Endpoint | Method | Purpose | Auth |
|---|---|---|---|
/api/health |
GET | Startup health state, including starting, ready, or degraded, registered tool count, and child service status. ready returns 200; starting returns 503. |
public |
/api/tools |
GET | Lists all registered tools as { tools: [...] }. |
public |
/api/tool/:toolName |
POST | Invokes a registered tool with the request body as tool arguments. This is a local agent convenience surface for cam, percept, memory, and broad tools. | Trusted local open. When REFLECTWORLD_PUBLIC_MODE=true or REFLECTWORLD_TOOL_API_RESTRICTED=true, it requires an admin token. POST body default limit is 1 MB. |
| Endpoint | Method | Request body | Response | Purpose |
|---|---|---|---|---|
/api/dashboard/session/start |
POST | session_id, user_id required; is_admin?, admin_token? |
{ ok, multi_user, session_id, user_id, max_active_sessions, max_sources_per_session, heartbeat_interval_ms, stale_ttl_ms }; single-user mode returns { ok:true, multi_user:false } |
Creates or resumes a dashboard session and sets vm_dashboard_session and vm_dashboard_user HttpOnly cookies. Full capacity returns 429; invalid admin token returns 403; conflicting session binding returns 409. |
/api/dashboard/session/heartbeat |
POST | session_id required |
{ ok, multi_user }; missing session returns 404 |
Refreshes lastSeen. |
/api/dashboard/session/end |
POST | session_id |
{ ok, multi_user } |
Ends a session by marking it stale for cleanup. |
/api/dashboard/runtime |
GET | none | { go2rtc_api_port, multi_user, max_active_sessions, max_sources_per_session } |
Small set of public runtime facts needed by the UI. |
In single-user mode, all session endpoints return multi_user:false and act as no-ops.
| Endpoint | Method | Parameters | Response | Auth |
|---|---|---|---|---|
/api/dashboard/state |
GET | none | { cursor, server_time, sources:[...], segments:[...] }, canonical snapshot/hydration |
public, filtered by session visibility |
/api/sources |
GET | none | { sources:[...] }, all dashboard-managed live sources |
public, filtered by session |
/api/sources/active |
GET | none | { sources:[...] }, only status==="active" sources |
public, filtered by session |
/api/segments/recent |
GET | limit? default 30 max 200, camera_id? |
{ events:[...] }, hydrated active video analysis events |
public; invisible camera returns empty |
/api/traces |
GET | limit? default 50 max 500, camera_id? |
{ traces:[...], count }, recent pipeline traces |
public, filtered by session |
/api/traces/stats |
GET | minutes? default 60, camera_id? |
P50/P95 aggregate statistics | public, filtered by session |
/api/semantic-context |
GET | camera_id required |
Current Semantic Context for the source; missing camera_id returns 400, invisible source returns 404 |
public, filtered by session visibility |
/api/analysis/status |
GET | none | { active:[...], recent:[...] }, active and recently completed video analysis |
public, filtered by session |
/api/videos |
GET | none | { videos:[...] }, uploaded and built-in demo videos |
public, uploaded videos filtered by session |
| Endpoint | Method | Parameters | Purpose | Auth |
|---|---|---|---|---|
/api/videos/file |
GET/HEAD | video_id, for example upload/foo.mp4 |
Browser preview of the selected original file, with Range support | public; upload/ visibility filtered by session, otherwise 404 |
/api/videos/preview |
GET/HEAD | video_id |
Browser-safe H.264 transcoded preview, generated and cached on demand | public, same visibility rules |
/api/videos/thumbnail |
GET/HEAD | video_id |
Still-image thumbnail for a video, generated and cached on demand | public, same visibility rules |
/media/* |
GET | media relative path | Static serving for generated media such as keyframes, GIFs, and crops. Segment responses use URLs like /media/.... |
public |
/api/segment/:segmentId/clip |
GET | Range header | Video clip for one segment | public, filtered by session visibility |
/ and other static paths |
GET | none | Dashboard SPA static assets from distDir; misses fall back to index.html |
public |
The task-list endpoint /api/keyframe/:id does not exist in current implementation. Keyframe images are served through /media/*.
These endpoints proxy go2rtc through the same origin to avoid browser cross-origin issues. All check source visibility by session and return 404 for invisible sources.
| Endpoint | Method | Parameters | Purpose |
|---|---|---|---|
/api/live/stream.mp4 |
GET | src, camera id |
Same-origin proxy for go2rtc fragmented MP4. |
/api/live/stream.mjpeg |
GET | src |
Same-origin proxy for go2rtc MJPEG. |
/api/live/status |
GET | src |
Lightweight stream health check that does not open a frame consumer. |
/api/live/frame.jpeg |
GET | src |
Same-origin proxy for a single frame preview. |
The following endpoints are mutations. Multi-user mode requires a session and returns 401 without one; single-user mode remains open. The source field on /api/streams/analyze and /api/sources is an SSRF and arbitrary-file-read boundary. Source security validation runs before cam add_source. Trusted single-user local mode stays easy by default; dashboard session requests, REFLECTWORLD_SOURCE_SECURITY_STRICT=true, or REFLECTWORLD_PUBLIC_MODE=true enable strict mode with scheme allowlist, DNS private/loopback rejection, and local file path rejection by default. See configuration for the full environment variable list.
| Endpoint | Method | Request body | Response | Purpose |
|---|---|---|---|---|
/api/analyze |
POST | video_id, fps? default 1 max 30, local_file_pacing? as fast or realtime, source_context? up to 4000 characters |
Analysis instance result including camera_id |
Adds a source for the selected video file and starts watch. |
/api/streams/analyze |
POST | source?, name required, mode? as stream or event, fps?, width?, height?, duration?, defer_watch?, source_context? |
Result including source_id/camera_id |
Dashboard wrapper around add_source + watch for live, RTSP, USB, phone, and similar sources. |
/api/sources |
POST | source URL required, name? |
Raw cam add_source result, {success,...} or 400 on failure |
Registers a new camera source through the cam tool. |
/api/sources/remove |
POST | name or source_id required, video_id? |
Removal result | Stops watch, then removes the source instance and source-scoped state. It does not delete uploaded files; uploads are deleted through /api/videos/delete. |
/api/sources/stop |
POST | name or source_id required |
Stop result | Stops watch while preserving source data/history. |
/api/videos/upload |
POST | multipart upload with one video file | Upload result | Uploads a local video, subject to the per-session source limit. |
/api/videos/delete |
POST | video_id |
Delete result | Deletes an uploaded video and its preview cache. |
/api/dashboard/clear |
POST | optional body | { ok:true } |
Clears uploaded videos and dashboard-visible analysis data. In multi-user mode, non-admin users only clear their own session sources. |
/api/config |
GET | none | Current runtimeConfig |
Reads runtime config. |
/api/config |
POST | { key, value }, with key in VALID_CONFIG_KEYS and value as string |
{ ok, key, value, previous }, invalid request returns 400 |
Updates one runtime config key for the running Dashboard process. |
Chat endpoints call an LLM and are cost-bearing DoS surfaces. In multi-user or public mode, they require a session and apply session/IP rate limiting through DASHBOARD_CHAT_RATE_LIMIT_PER_MIN. Trusted single-user local mode has no rate limit by default.
| Endpoint | Method | Request body | Response | Purpose |
|---|---|---|---|---|
/api/dashboard/chat |
POST | prompt required up to 2000 characters, camera_id?, history? up to 8 {role,text} entries, stream? true for SSE |
Non-streaming { answer, ... }; streaming text/event-stream with status, delta, error, and done events |
Lightweight Q&A against current dashboard context. |
/api/dashboard/live-agent/config |
GET | none | Live-agent browser config, or enabled:false when disabled |
Experimental realtime dashboard agent configuration for clients that open /ws/live-agent. |
/v1/chat/completions, /api/openai/v1/chat/completions |
POST | OpenAI-style { messages, model?, ... } |
OpenAI-compatible completion | OpenAI-compatible wrapper. Public deployments should validate or restrict client-provided model. |
Expected auth direction: proxy endpoints must require a dashboard session in multi-user mode. user_id should be derived by the server from the session, not accepted from the client. Dangerous methods such as /reset and DELETE should be denied by default or admin-gated. In current single-user mode, delete forwarding strips user_id to avoid hiding default-user memories; multi-user mode preserves user_id as the isolation key.
| Endpoint | Method | Target | Purpose | Auth |
|---|---|---|---|---|
/api/reflectworld/* |
any | mem service endpoints from the first section, with the /api/reflectworld prefix removed |
Browser-side memory and ReID reads/writes. The proxy strips session_id, strips user_id in single-user mode, and rewrites responses through rewriteReflectWorldProxyResponse, such as entity_id to display ref. |
session in multi-user |
| Endpoint | Method | Request body | Purpose | Auth |
|---|---|---|---|---|
/api/memory-event |
POST | { type?, data:{ camera_id?, ... } }; type must be in the MEMORY_WEBHOOK_EVENT_TYPES allowlist |
The mem service calls this when memory changes; the dashboard converts it into monitorBus events for the UI. |
loopback-only. Non-loopback requests return 403 through isLoopbackRequest. |
| Endpoint | Protocol | Purpose | Auth |
|---|---|---|---|
/ws/dashboard |
WS | Canonical product state stream. On connect, sends a __connected__ envelope, replays by cursor, sends initial source/segment projection, then streams versioned envelopes with domain, id, version, op, and scope. |
Filters visible envelopes by session through scopeFromRequest. |
/ws/monitor |
WS | Raw realtime monitor event stream for diagnostics and compatibility. Product state should migrate to /ws/dashboard. |
Filters by session. |
/ws/live-agent |
WS | Experimental realtime dashboard agent relay. | Session-gated in multi-user/public mode; disabled unless live-agent config is enabled. |
WS has maxPayload=64KB. In multi-user or public mode it validates Origin, allowing the same host by default and extra origins from DASHBOARD_WS_ALLOWED_ORIGINS, and limits total connections through DASHBOARD_MAX_WS_CONNECTIONS to reduce CSWSH and connection-flood risk.
| Category | Single-user local | Multi-user / public |
|---|---|---|
| Read-only projection: state, sources, traces, videos, semantic-context, live, media | Open | Open, but visibility is filtered by session. |
| Mutations: analyze, sources, config, upload, clear | Open | Session required, otherwise 401. |
| Chat: dashboard chat and OpenAI-compatible wrapper | Open | Session required plus rate limit. |
Proxy: /api/reflectworld/* |
Open | Session required; user_id derived server-side. |
| Webhook: memory-event | loopback-only | loopback-only |
Host tools: /api/tool/:name |
Open | Disabled by default or admin-token gated. |
| Mem service endpoints | loopback, internal calls only | Shared token plus loopback binding. |