{"openapi":"3.1.0","info":{"title":"Ocean Song API","description":"Real-time cetacean acoustic monitoring API.\n\nOcean Song monitors 7 live hydrophone streams worldwide, running ML detection\nfor humpback whales, orcas, blue whales, and sperm whales. Detected events\nare recorded as FLAC clips with spectrograms and optional ESP biodenoising.\n\n**Dashboard**: https://oceansong.live\n**WebSocket**: wss://api.oceansong.live/ws (real-time status + events)\n\n## Streams\n- 5× Orcasound HLS (Pacific NW)\n- 1× MBARI MARS Shoutcast (Monterey Bay, 900m depth)\n- 1× SIMRES Icecast (Saturna Island, BC)\n\n## Detection Models\n- Humpback whale (Google TF Hub, 90% threshold, 2 consecutive windows)\n- Orca (orcAI / ETH Zurich, 50% threshold)\n- Blue whale A-call (MBARI ResNet50, 70% threshold)\n- Sperm whale clicks (spectrogram band power, 70% threshold)\n","version":"2.0.0"},"paths":{"/stream-proxy/{stream_id}":{"get":{"summary":"Proxy a live stream for browser playback","description":"Proxies an Icecast/Shoutcast stream through our API to add CORS headers. Use when the origin stream server doesn't set Access-Control-Allow-Origin.","operationId":"stream_proxy_stream_proxy__stream_id__get","parameters":[{"name":"stream_id","in":"path","required":true,"schema":{"type":"string","title":"Stream Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/summary":{"get":{"summary":"Quick system summary for agents","description":"One-call overview: streams online count, total detections, active models, and the single most recent detection. Designed for agents that need a fast situation report.","operationId":"get_summary_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/highlights":{"get":{"summary":"Top detections and activity breakdown","description":"Aggregated highlights over a time window: top N detections by confidence, per-species counts, per-stream counts, and the latest event. Perfect for agents building reports or dashboards.","operationId":"get_highlights_highlights_get","parameters":[{"name":"hours","in":"query","required":false,"schema":{"type":"integer","description":"Time window in hours (default 24)","default":24,"title":"Hours"},"description":"Time window in hours (default 24)"},{"name":"top","in":"query","required":false,"schema":{"type":"integer","description":"Number of top detections to return","default":5,"title":"Top"},"description":"Number of top detections to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/activity-patterns":{"get":{"summary":"Hourly detection patterns per stream","description":"Returns detection counts grouped by hour of day (0-23) for each stream, optionally broken down by species. Use the period parameter to select the time window. Useful for identifying temporal patterns — when are whales most active at each location?","operationId":"get_activity_patterns_activity_patterns_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Time window: 24h, 7d, or 30d","default":"7d","title":"Period"},"description":"Time window: 24h, 7d, or 30d"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/status":{"get":{"summary":"System status and stream telemetry","description":"Returns connection status, detection scores, and telemetry for all monitored streams. Includes per-stream last detection time, detection counts (1h/24h/7d), and top species.","operationId":"get_status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/events":{"get":{"summary":"List detection events","description":"Returns paginated detection events, newest first. Filter by stream, species, or time range. Species can be: humpback, orca, blue_whale, sperm_whale. Events with multiple species use comma separation.","operationId":"get_events_events_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp — only events after this time","title":"Since"},"description":"ISO timestamp — only events after this time"},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp — only events before this time (for pagination)","title":"Before"},"description":"ISO timestamp — only events before this time (for pagination)"},{"name":"stream","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stream ID filter (e.g. mbari-mars, simres, orcasound-pt)","title":"Stream"},"description":"Stream ID filter (e.g. mbari-mars, simres, orcasound-pt)"},{"name":"species","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Species filter (e.g. humpback, orca). Matches within comma-separated species fields.","title":"Species"},"description":"Species filter (e.g. humpback, orca). Matches within comma-separated species fields."},{"name":"min_snr","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum SNR in dB (inclusive)","title":"Min Snr"},"description":"Minimum SNR in dB (inclusive)"},{"name":"max_snr","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum SNR in dB (exclusive)","title":"Max Snr"},"description":"Maximum SNR in dB (exclusive)"},{"name":"min_vocal_clarity","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum vocal clarity in dB (whale-band SNR, 200-4kHz)","title":"Min Vocal Clarity"},"description":"Minimum vocal clarity in dB (whale-band SNR, 200-4kHz)"},{"name":"min_tonality","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum tonality (0-1, higher = more tonal/whale-like)","title":"Min Tonality"},"description":"Minimum tonality (0-1, higher = more tonal/whale-like)"},{"name":"min_signal_level","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum signal level in dBFS (higher = closer whale)","title":"Min Signal Level"},"description":"Minimum signal level in dBFS (higher = closer whale)"},{"name":"max_noise_floor","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum noise floor in dBFS (lower = quieter background)","title":"Max Noise Floor"},"description":"Maximum noise floor in dBFS (lower = quieter background)"},{"name":"max_impulse_ratio","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum impulse ratio (lower = fewer broadband bangs; 1=clean, 2+=bangs)","title":"Max Impulse Ratio"},"description":"Maximum impulse ratio (lower = fewer broadband bangs; 1=clean, 2+=bangs)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"description":"Max events to return (default 10, max 1000)","default":10,"title":"Limit"},"description":"Max events to return (default 10, max 1000)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/by-ids":{"post":{"summary":"Fetch specific events by ID list","description":"Returns events matching the given IDs. Used for loading favorited events.","operationId":"get_events_by_ids_events_by_ids_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Ids"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}":{"get":{"summary":"Get a single event by ID","description":"Returns a single event with clip_available and has_denoised flags. Used for deep-linking to specific events (e.g. /denoise/7254).","operationId":"get_event_by_id_events__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clips/{event_id}":{"get":{"summary":"Get Clip","operationId":"get_clip_clips__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clips/{event_id}/mp3":{"get":{"summary":"Get Clip Mp3","operationId":"get_clip_mp3_clips__event_id__mp3_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clips/{event_id}/denoised/mp3":{"get":{"summary":"Get Clip Denoised Mp3","description":"Serve denoised clip as MP3.","operationId":"get_clip_denoised_mp3_clips__event_id__denoised_mp3_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clips/{event_id}/denoised":{"get":{"summary":"Get Clip Denoised","description":"Download denoised clip as FLAC.","operationId":"get_clip_denoised_clips__event_id__denoised_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/spectrograms/{event_id}":{"get":{"summary":"Get Spectrogram","operationId":"get_spectrogram_spectrograms__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/denoise/{event_id}":{"get":{"summary":"On-demand denoising with configurable parameters","description":"Process a clip through noisereduce + biodenoising with custom parameters. Returns MP3 audio. Use step=raw|noisereduce|biodenoising to get intermediate results.","operationId":"denoise_clip_denoise__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}},{"name":"step","in":"query","required":false,"schema":{"type":"string","description":"Pipeline step to return: raw, noisereduce, biodenoising","default":"biodenoising","title":"Step"},"description":"Pipeline step to return: raw, noisereduce, biodenoising"},{"name":"dry","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Biodenoising dry/wet mix (0=fully denoised, 1=original)","default":0.1,"title":"Dry"},"description":"Biodenoising dry/wet mix (0=fully denoised, 1=original)"},{"name":"nr_enabled","in":"query","required":false,"schema":{"type":"boolean","description":"Enable noisereduce preprocessing","default":true,"title":"Nr Enabled"},"description":"Enable noisereduce preprocessing"},{"name":"nr_stationary","in":"query","required":false,"schema":{"type":"boolean","description":"Stationary noise estimation (True for steady noise, False for varying)","default":true,"title":"Nr Stationary"},"description":"Stationary noise estimation (True for steady noise, False for varying)"},{"name":"nr_prop_decrease","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Noise reduction strength (0=none, 1=full)","default":0.75,"title":"Nr Prop Decrease"},"description":"Noise reduction strength (0=none, 1=full)"},{"name":"nr_freq_smooth_hz","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":50,"description":"Frequency mask smoothing (Hz)","default":250,"title":"Nr Freq Smooth Hz"},"description":"Frequency mask smoothing (Hz)"},{"name":"nr_time_smooth_ms","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":5,"description":"Time mask smoothing (ms)","default":30,"title":"Nr Time Smooth Ms"},"description":"Time mask smoothing (ms)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/denoise-manifest/{event_id}":{"post":{"summary":"Full denoise pipeline with spectrograms and metrics","description":"Runs all 3 pipeline steps (raw, noisereduce, biodenoising) with given parameters, generates spectrograms and quality metrics for each step. Results are cached on disk. Returns a JSON manifest with URLs for audio/spectrograms and numeric metrics.","operationId":"denoise_manifest_denoise_manifest__event_id__post","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}},{"name":"dry","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"default":0.1,"title":"Dry"}},{"name":"nr_enabled","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Nr Enabled"}},{"name":"nr_stationary","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Nr Stationary"}},{"name":"nr_prop_decrease","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"default":0.75,"title":"Nr Prop Decrease"}},{"name":"nr_freq_smooth_hz","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":50,"default":250,"title":"Nr Freq Smooth Hz"}},{"name":"nr_time_smooth_ms","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":5,"default":30,"title":"Nr Time Smooth Ms"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/denoise-cache/{event_id}/{params_hash}/{step}/mp3":{"get":{"summary":"Get Denoise Cached Mp3","operationId":"get_denoise_cached_mp3_denoise_cache__event_id___params_hash___step__mp3_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}},{"name":"params_hash","in":"path","required":true,"schema":{"type":"string","title":"Params Hash"}},{"name":"step","in":"path","required":true,"schema":{"type":"string","title":"Step"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/denoise-cache/{event_id}/{params_hash}/{step}/spectrogram":{"get":{"summary":"Get Denoise Cached Spectrogram","operationId":"get_denoise_cached_spectrogram_denoise_cache__event_id___params_hash___step__spectrogram_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}},{"name":"params_hash","in":"path","required":true,"schema":{"type":"string","title":"Params Hash"}},{"name":"step","in":"path","required":true,"schema":{"type":"string","title":"Step"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/separate/{event_id}":{"post":{"summary":"Source separation with ConvTasNet","description":"Separate overlapping vocalizations into individual sources using asteroid's pretrained ConvTasNet (16kHz, 2-source). Results are cached. Returns a manifest with URLs to separated source MP3s.","operationId":"separate_clip_separate__event_id__post","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clips/{event_id}/separated/{source_idx}/mp3":{"get":{"summary":"Get separated source as MP3","operationId":"get_separated_source_mp3_clips__event_id__separated__source_idx__mp3_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}},{"name":"source_idx","in":"path","required":true,"schema":{"type":"integer","title":"Source Idx"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/clips/{event_id}/separated/{source_idx}":{"get":{"summary":"Get separated source as FLAC","operationId":"get_separated_source_flac_clips__event_id__separated__source_idx__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}},{"name":"source_idx","in":"path","required":true,"schema":{"type":"integer","title":"Source Idx"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}