{"openapi":"3.1.0","info":{"title":"Envqor Realtime Sound Event Detect API","description":"\nEnvqor realtime environmental sound event detection API.\n\nHTTP endpoints are available in this Swagger/OpenAPI document. Realtime audio is streamed over WebSocket at `/ws/audio`\nfor raw Float32 PCM or `/ws/audio/opus` for Opus chunks.\nOffline WAV/MP3 analysis is available at `POST /wav/analyze` for files up to 180 seconds.\nBrowser-side TensorFlow Lite YAMNet results can be persisted with `POST /client-inference/yamnet`.\nThe `/local-yamnet` Web page uses these HTTP APIs:\n\n- `POST /client-inference/yamnet` to store each 2-second browser-side inference result.\n- `GET /scenes/recent` to reload recent 10-second scene summaries for the current user/device.\n- `GET /detections/recent` to inspect recent Top-30 detection rows for troubleshooting.\n\nAudio WebSocket summary:\n\n1. Connect to `WS /ws/audio` for raw Float32 PCM or `WS /ws/audio/opus` for Opus chunks.\n2. Wait for a `ready` message containing `session_id`.\n3. Before each audio binary frame, send an `audio_metadata` JSON text frame.\n4. Send mono raw Float32 PCM bytes, or an Opus chunk such as WebM/Opus or Ogg/Opus, about 2 seconds per frame.\n5. Receive a `detection` JSON message with Top-30 YAMNet predictions.\n6. Every fifth 2-second frame, the response also includes `scene_10s`.\n\nYAMNet target format is mono waveform at 16 kHz with float values in `[-1.0, 1.0]`.\nThe API accepts common browser sample rates such as 44.1 kHz or 48 kHz and resamples server-side.\n","version":"0.1.0"},"paths":{"/health":{"get":{"tags":["Health"],"summary":"Get API and YAMNet runtime status","description":"Returns API readiness plus whether real YAMNet inference is enabled and loaded.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}},"/service/status":{"get":{"tags":["Service Status"],"summary":"Get Backend service and runtime status","description":"Returns API health, YAMNet runtime state, active WebSocket session count, runtime device counts, and recent WebSocket session errors for Backend-to-Backend monitoring.","operationId":"service_status_service_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Service Status Service Status Get"}}}}},"security":[{"ServiceBackendApiKey":[]}]}},"/service/devices":{"get":{"tags":["Service Status"],"summary":"List devices with online/offline state","description":"Returns device inventory for Backend monitoring. Precise status is available for devices that have connected after runtime status tracking was enabled; older devices may be reported as unknown.","operationId":"service_devices_service_devices_get","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional user filter. Omit only for trusted Backend calls.","title":"User Id"},"description":"Optional user filter. Omit only for trusted Backend calls."},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(online|offline|unknown|all)$","default":"all","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Service Devices Service Devices Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/service/devices/{device_id}/status":{"get":{"tags":["Service Status"],"summary":"Get one device status","description":"Returns runtime status, active session count, and latest record timestamps for one device.","operationId":"service_device_status_service_devices__device_id__status_get","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional user filter. Recommended for tenant isolation.","title":"User Id"},"description":"Optional user filter. Recommended for tenant isolation."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Service Device Status Service Devices  Device Id  Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/service/sessions/active":{"get":{"tags":["Service Status"],"summary":"List active WebSocket sessions","description":"Returns WebSocket sessions that have not disconnected yet.","operationId":"service_active_sessions_service_sessions_active_get","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Service Active Sessions Service Sessions Active Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/service/records/recent":{"get":{"tags":["Service Status"],"summary":"List recent detection and scene records for Backend monitoring","description":"Returns a combined lightweight feed of recent 2-second detections and 10-second scene summaries. Each record includes `source_encoding` so approved Backend clients can distinguish server-side WebSocket inference (`pcm_f32le`, `opus`, `opus_packet`, `opus_packet_batch`) from browser-side TensorFlow Lite inference (`browser_tflite_yamnet`).","operationId":"service_recent_records_service_records_recent_get","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Service Recent Records Service Records Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/service/api-keys":{"get":{"tags":["Service Status"],"summary":"List approved third-party Backend API keys","description":"Lists registered Backend API keys without exposing raw secrets. Environment bootstrap keys are not listed.","operationId":"service_api_keys_service_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Service Api Keys Service Api Keys Get"}}}}},"security":[{"ServiceBackendApiKey":[]}]},"post":{"tags":["Service Status"],"summary":"Create an approved third-party Backend API key","description":"Creates a database-backed Backend API key. The raw key is returned once and only its SHA-256 hash is stored in PostgreSQL.","operationId":"create_service_api_key_service_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceApiKeyCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Service Api Key Service Api Keys Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ServiceBackendApiKey":[]}]}},"/service/api-keys/{key_id}":{"patch":{"tags":["Service Status"],"summary":"Update a third-party Backend API key registration","operationId":"update_service_api_key_service_api_keys__key_id__patch","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceApiKeyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Service Api Key Service Api Keys  Key Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/service/api-keys/{key_id}/revoke":{"post":{"tags":["Service Status"],"summary":"Revoke a third-party Backend API key","operationId":"revoke_service_api_key_service_api_keys__key_id__revoke_post","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Service Api Key Service Api Keys  Key Id  Revoke Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/service/api-keys/{key_id}/rotate":{"post":{"tags":["Service Status"],"summary":"Rotate a third-party Backend API key","description":"Revokes the selected key and creates a new key for the same partner. The new raw key is returned once.","operationId":"rotate_service_api_key_service_api_keys__key_id__rotate_post","security":[{"ServiceBackendApiKey":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceApiKeyRotate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Rotate Service Api Key Service Api Keys  Key Id  Rotate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/detections/recent":{"get":{"tags":["Detections"],"summary":"List recent 2-second detection logs","description":"Returns recent detection rows from PostgreSQL. Each row contains the WebSocket session, trace id, chunk metadata, Top-30 YAMNet predictions, RMS, inference duration, and original metadata JSON. The MVP requires user_id so third-party clients only read their own rows.","operationId":"recent_detections_detections_recent_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate query results.","title":"User Id"},"description":"Required MVP user id used to isolate query results."},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional device id filter.","title":"Device Id"},"description":"Optional device id filter."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of recent detection rows to return.","default":20,"title":"Limit"},"description":"Maximum number of recent detection rows to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Recent Detections Detections Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scenes/recent":{"get":{"tags":["Scenes"],"summary":"List recent 10-second scene aggregates","description":"Returns scene rows aggregated from the latest five 2-second inference records in each session. The response includes construction, machinery, impact, speech, indoor public, vehicle, home, outdoor, walking scores, scene label, confidence, and event summary. The MVP requires user_id so third-party clients only read their own rows.","operationId":"recent_scenes_scenes_recent_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate query results.","title":"User Id"},"description":"Required MVP user id used to isolate query results."},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional device id filter.","title":"Device Id"},"description":"Optional device id filter."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of recent scene rows to return.","default":20,"title":"Limit"},"description":"Maximum number of recent scene rows to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Recent Scenes Scenes Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/client-inference/yamnet":{"post":{"tags":["Client Inference"],"summary":"Persist a browser-side TensorFlow Lite YAMNet inference result","description":"Receives a 2-second YAMNet inference result produced in the browser. The API validates the payload, stores detection and inference rows, and reuses the existing 10-second scene aggregation. Raw audio is not uploaded for this endpoint. Every fifth accepted 2-second chunk returns `scene_10s`; earlier chunks return `scene_10s: null` while the 10-second window is still accumulating. Request bodies larger than 131072 bytes are rejected. These records are marked as `trust_level=client_submitted` because the API receives inference results rather than raw audio.","operationId":"ingest_client_yamnet_inference_client_inference_yamnet_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientYamnetIngestRequest"},"examples":{"browser_tflite_yamnet":{"summary":"Browser TensorFlow Lite YAMNet 2-second inference result","description":"This is the payload sent by `/local-yamnet` after the browser runs local YAMNet inference. It contains Top predictions and audio statistics, but no raw audio. The API stores it as client-submitted measurement data.","value":{"user_id":"browser-user-demo","device_id":"browser-device-demo","session_id":"local-session-demo","trace_id":"trace-demo-0001","chunk_index":1,"source":"browser_tflite_yamnet","model":{"name":"yamnet","runtime":"tfjs-tflite","model_url":"/models/yamnet_classification.tflite","model_version":"mediapipe-yamnet-float32-v1"},"audio":{"sample_rate":16000,"duration_seconds":2.0,"samples_count":32000,"rms":0.024,"dbfs_mean":-32.4},"inference":{"client_inference_ms":38.5,"preprocess_ms":6.2,"top_predictions":[{"rank":1,"label":"Speech","score":0.812345,"class_index":0},{"rank":2,"label":"Inside, small room","score":0.204321,"class_index":55},{"rank":3,"label":"Silence","score":0.102112,"class_index":494}],"scores":[0.812345,0.204321,0.102112]},"metadata":{"browser":"Chrome","input_device_label":"MacBook Pro Microphone","chunk_started_at":"2026-05-16T06:00:00.000Z","chunk_ended_at":"2026-05-16T06:00:02.000Z","client_sent_at":"2026-05-16T06:00:02.050Z"}}}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ingest Client Yamnet Inference Client Inference Yamnet Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices":{"get":{"tags":["Device Profiles"],"summary":"List devices for a user","description":"Returns device ids seen in persisted realtime data or device scene profiles for the given user.","operationId":"list_devices_devices_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate devices.","title":"User Id"},"description":"Required MVP user id used to isolate devices."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Devices Devices Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices/{device_id}/scene-profile/templates":{"get":{"tags":["Device Profiles"],"summary":"List scene profile templates","description":"Returns predefined scene profile templates that clients can load into an editor before saving. Loading a template does not create a device profile revision until the client saves it.","operationId":"list_device_scene_profile_templates_devices__device_id__scene_profile_templates_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Device Scene Profile Templates Devices  Device Id  Scene Profile Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices/{device_id}/scene-profile":{"get":{"tags":["Device Profiles"],"summary":"Get active scene profile for a device","description":"Returns the saved device profile, or a default profile when the device has no saved override.","operationId":"get_device_scene_profile_devices__device_id__scene_profile_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Device Scene Profile Devices  Device Id  Scene Profile Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Device Profiles"],"summary":"Save scene profile for a device","description":"Stores per-device scene thresholds, weights, enabled flags, and scene priority. The saved config is merged with the default profile so clients can send partial overrides.","operationId":"upsert_device_scene_profile_devices__device_id__scene_profile_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SceneProfileUpsert"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Upsert Device Scene Profile Devices  Device Id  Scene Profile Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices/{device_id}/scene-profile/reset":{"post":{"tags":["Device Profiles"],"summary":"Reset a device scene profile to defaults","operationId":"reset_device_scene_profile_devices__device_id__scene_profile_reset_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reset Device Scene Profile Devices  Device Id  Scene Profile Reset Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices/{device_id}/scene-profile/revisions":{"get":{"tags":["Device Profiles"],"summary":"List saved scene profile revisions for a device","description":"Returns historical device profile snapshots. Use a revision id to restore a previous setting.","operationId":"list_device_scene_profile_revisions_devices__device_id__scene_profile_revisions_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Device Scene Profile Revisions Devices  Device Id  Scene Profile Revisions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices/{device_id}/scene-profile/revisions/{revision_id}/restore":{"post":{"tags":["Device Profiles"],"summary":"Restore a previous scene profile revision","description":"Loads a historical profile snapshot into the active device profile. The restore operation also creates a new revision so the change is auditable.","operationId":"restore_device_scene_profile_revision_devices__device_id__scene_profile_revisions__revision_id__restore_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"revision_id","in":"path","required":true,"schema":{"type":"integer","title":"Revision Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SceneProfileRevisionRestore"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Restore Device Scene Profile Revision Devices  Device Id  Scene Profile Revisions  Revision Id  Restore Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/devices/{device_id}/scene-profile/preview":{"post":{"tags":["Device Profiles"],"summary":"Preview a scene profile against recent device inference records","description":"Fetches the latest five 2-second inference records for the device and returns the scene result with the saved/default profile plus the provided draft config.","operationId":"preview_device_scene_profile_devices__device_id__scene_profile_preview_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Required MVP user id used to isolate profile data.","title":"User Id"},"description":"Required MVP user id used to isolate profile data."}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SceneProfilePreviewRequest"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Preview Device Scene Profile Devices  Device Id  Scene Profile Preview Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/integration/websocket-contract":{"get":{"tags":["Integration"],"summary":"Get WebSocket audio streaming contract","description":"OpenAPI does not fully describe WebSocket message flows, so this endpoint exposes the realtime audio protocol in a Swagger-visible JSON contract for third-party integrators.","operationId":"websocket_contract_integration_websocket_contract_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Websocket Contract Integration Websocket Contract Get"}}}}}}},"/wav/analyze":{"post":{"tags":["Batch Analysis"],"summary":"Analyze an uploaded WAV or MP3 file","description":"Accepts one WAV or MP3 file up to 180 seconds long. The API decodes audio, splits it into 2-second chunks, runs YAMNet inference for each chunk, aggregates every five chunks into a 10-second scene summary, writes the detection, inference, and scene records to PostgreSQL, and returns a single statistics JSON response. The uploaded audio file itself is not stored.\n\nSwagger UI usage: choose a `.wav` or `.mp3` file in the `file` field, optionally enter `user_id` and `device_id`, then execute the request.","operationId":"analyze_wav_wav_analyze_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_wav_wav_analyze_post"}}},"required":true},"responses":{"200":{"description":"Audio analysis completed successfully.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Analyze Wav Wav Analyze Post"},"example":{"type":"audio_file_analysis","analysis_id":"audio-00000000-0000-4000-8000-000000000000","user_id":"test-user","device_id":"test-device","filename":"sample.mp3","audio":{"format":"mp3","sample_rate":16000,"channels":1,"duration_seconds":12.0,"chunk_seconds":2.0,"scene_window_seconds":10.0,"max_duration_seconds":180.0},"summary":{"chunk_count":6,"scene_window_count":1,"unaggregated_tail_chunks":1,"average_inference_ms":28.958,"dominant_scene_label":"indoor_public_space","scene_label_counts":{"indoor_public_space":1},"top_labels":[{"label":"Speech","count":6,"mean_score":0.84,"max_score":0.91}]},"persistence":{"stored":true,"session_id":"audio-00000000-0000-4000-8000-000000000000","detection_log_count":6,"inference_2s_count":6,"scene_10s_count":1},"detections_2s":[{"chunk_index":1,"start_seconds":0.0,"end_seconds":2.0,"top_predictions":[{"rank":1,"class_index":0,"label":"Speech","score":0.91}],"rms":0.024,"inference_ms":28.4}],"scene_summaries_10s":[{"window_index":1,"start_seconds":0.0,"end_seconds":10.0,"scene_label":"indoor_public_space","confidence":0.74,"speech_score":0.74,"indoor_public_score":0.54}]}}}},"400":{"description":"Invalid upload, unsupported format, empty file, or audio duration over 180 seconds.","content":{"application/json":{"example":{"detail":"Audio duration must be <= 180 seconds"}}}},"422":{"description":"Missing multipart form field or invalid request body."}}}}},"components":{"schemas":{"Body_analyze_wav_wav_analyze_post":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"PCM WAV or MP3 file. Maximum duration: 180 seconds."},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Optional user id echoed in the analysis JSON."},"device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id","description":"Optional device id echoed in the analysis JSON."}},"type":"object","required":["file"],"title":"Body_analyze_wav_wav_analyze_post"},"ClientYamnetAudioInfo":{"properties":{"sample_rate":{"type":"integer","maximum":192000.0,"minimum":1.0,"title":"Sample Rate"},"duration_seconds":{"type":"number","maximum":5.0,"exclusiveMinimum":0.0,"title":"Duration Seconds"},"samples_count":{"type":"integer","maximum":192000.0,"minimum":1.0,"title":"Samples Count"},"rms":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Rms"},"dbfs_mean":{"anyOf":[{"type":"number","maximum":20.0,"minimum":-240.0},{"type":"null"}],"title":"Dbfs Mean"}},"type":"object","required":["sample_rate","duration_seconds","samples_count","rms"],"title":"ClientYamnetAudioInfo"},"ClientYamnetInferenceInfo":{"properties":{"client_inference_ms":{"type":"number","maximum":60000.0,"minimum":0.0,"title":"Client Inference Ms"},"preprocess_ms":{"anyOf":[{"type":"number","maximum":60000.0,"minimum":0.0},{"type":"null"}],"title":"Preprocess Ms"},"top_predictions":{"items":{"$ref":"#/components/schemas/ClientYamnetPrediction"},"type":"array","maxItems":30,"minItems":1,"title":"Top Predictions"},"scores":{"anyOf":[{"items":{"type":"number"},"type":"array","maxItems":1024},{"type":"null"}],"title":"Scores"},"embedding_mean":{"anyOf":[{"items":{"type":"number"},"type":"array","maxItems":4096},{"type":"null"}],"title":"Embedding Mean"},"embedding_std":{"anyOf":[{"items":{"type":"number"},"type":"array","maxItems":4096},{"type":"null"}],"title":"Embedding Std"}},"type":"object","required":["client_inference_ms","top_predictions"],"title":"ClientYamnetInferenceInfo"},"ClientYamnetIngestRequest":{"properties":{"user_id":{"type":"string","maxLength":160,"minLength":1,"title":"User Id"},"device_id":{"type":"string","maxLength":160,"minLength":1,"title":"Device Id"},"session_id":{"type":"string","maxLength":160,"minLength":1,"title":"Session Id"},"trace_id":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Trace Id"},"chunk_index":{"type":"integer","minimum":1.0,"title":"Chunk Index"},"source":{"type":"string","maxLength":80,"title":"Source","default":"browser_tflite_yamnet"},"model":{"$ref":"#/components/schemas/ClientYamnetModelInfo"},"audio":{"$ref":"#/components/schemas/ClientYamnetAudioInfo"},"inference":{"$ref":"#/components/schemas/ClientYamnetInferenceInfo"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["user_id","device_id","session_id","chunk_index","audio","inference"],"title":"ClientYamnetIngestRequest"},"ClientYamnetModelInfo":{"properties":{"name":{"type":"string","maxLength":80,"title":"Name","default":"yamnet"},"runtime":{"type":"string","maxLength":80,"title":"Runtime","default":"tfjs-tflite"},"model_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Model Url"},"model_version":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Model Version"}},"type":"object","title":"ClientYamnetModelInfo"},"ClientYamnetPrediction":{"properties":{"rank":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Rank"},"label":{"type":"string","maxLength":160,"minLength":1,"title":"Label"},"score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Score"},"class_index":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Class Index"}},"type":"object","required":["rank","label","score"],"title":"ClientYamnetPrediction"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"SceneProfilePreviewRequest":{"properties":{"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"}},"type":"object","title":"SceneProfilePreviewRequest"},"SceneProfileRevisionRestore":{"properties":{"profile_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Profile Name"}},"type":"object","title":"SceneProfileRevisionRestore"},"SceneProfileUpsert":{"properties":{"profile_name":{"type":"string","maxLength":120,"title":"Profile Name","default":"Custom"},"config":{"additionalProperties":true,"type":"object","title":"Config"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","title":"SceneProfileUpsert"},"ServiceApiKeyCreate":{"properties":{"partner_name":{"type":"string","maxLength":160,"title":"Partner Name"},"notes":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Notes"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","required":["partner_name"],"title":"ServiceApiKeyCreate"},"ServiceApiKeyRotate":{"properties":{"notes":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Notes"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","title":"ServiceApiKeyRotate"},"ServiceApiKeyUpdate":{"properties":{"partner_name":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Partner Name"},"notes":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Notes"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"ServiceApiKeyUpdate"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"ServiceBackendApiKey":{"type":"apiKey","description":"Backend-to-Backend API key required for trusted `/service/*` endpoints.","in":"header","name":"X-Envqor-Backend-Key"}}},"tags":[{"name":"Health","description":"Service readiness and YAMNet runtime status."},{"name":"Detections","description":"Recent 2-second detection logs persisted in PostgreSQL."},{"name":"Scenes","description":"Recent 10-second scene aggregates derived from five 2-second inference windows."},{"name":"Device Profiles","description":"Per-device editable scene thresholds, weights, priority, and preview APIs."},{"name":"Service Status","description":"Backend-to-Backend service, device, and WebSocket session status APIs."},{"name":"Batch Analysis","description":"Offline WAV/MP3 upload analysis using 2-second YAMNet chunks and 10-second scene summaries."},{"name":"Client Inference","description":"Browser-side TensorFlow Lite YAMNet inference result ingestion."},{"name":"Integration","description":"Third-party integration contracts, including the WebSocket audio streaming protocol."}]}