Features

Everything Otavi can do today.

Audio Capture

Deploy microphones anywhere — Raspberry Pi nodes or Android phones — and let them record autonomously.

  • 3-second WAV chunks at 48kHz mono (BirdNET's native window)
  • Three capture modes: ALSA (real hardware), simulated (sine wave), replay (pre-recorded files)
  • Local outbox queue (sql.js SQLite) — never blocks on network, drains with exponential backoff
  • MQTT transport with auto-reconnect, QoS 1, and hub-side acknowledgment
  • On-device audio filtering: an RMS silence gate and the YAMNet bird gate (per-category bird-likelihood) reject silence and non-bird audio before upload. The audio filter switch turns both off, on phones and Pis alike Pi 1.6.5: every chunk then goes on to BirdNET. The per-category drops (amphibian, insect, anthropogenic, human voice, other animal) follow the settings registry like every setting a satellite receives: set by the hub for every team, by a team for its satellites, by a satellite for itself, each level able to lock the one below
  • Recording profiles: continuous, dawn chorus, night migration, low power — with sunrise/sunset scheduling from GPS coordinates (NOAA algorithm)
  • 30-second heartbeat keepalive (decouples online status from audio sending)

AI Inference

BirdNET deep learning model identifies 6,500+ bird species.

  • Geo-aware filtering: species range narrowing based on GPS + date
  • Confidence-based re-processing: uncertain detections (0.4–0.7) get a second pass with extended window, frequency isolation, and cross-chunk correlation
  • Scalable workers: Python 3.11 + TFLite, scale horizontally with docker compose up --scale worker=N
  • Worker dashboard: connected workers, queue depth, processing stats

Species Intelligence

Rich context around every detection.

  • Species catalog: sortable cards with detection count, confidence, first/last seen, satellite count, accuracy. Sort by name uses primary language; search works across all 38 languages. Respects the tenant's detection-visibility toggles — hidden categories (frogs, machine noise, …) never appear here or in any list, map, chart, or report
  • Species profile pages: 90-day trend, hourly activity, seasonal presence, satellite breakdown, confidence distribution, share and comment on detections
  • Species notes + facts hub 0.42.0: an "About" card with the Wikipedia intro in your language (CC BY-SA, linked attribution) plus trait badges — conservation status (shown only when above "Least concern"), wingspan, weight, wing chord, habitat, diet, and migration behavior, combining Wikidata with the AVONET morphology dataset (Tobias et al. 2022, CC BY)
  • Expected species: eBird integration cross-references what you should be seeing vs. what you've detected
  • First-of-season detection: automatic seasonal tracking with green badge on detection cards
  • First-of-day detection: per-species per-satellite daily tracking with blue badge
  • Rarity model: frequency-based rarity scoring with watchlist alerts. Effort-aware hub 0.32: the eBird-driven rarity flag carries a rarity_confidence ∈ {high, medium, low} based on nearby checklist count from the same /recent payload — high ≥50, medium 10–49, low 3–9, no flag below 3. Per Johnston 2021, "no nearby observations" at low-effort sites is just sampling noise rather than evidence of rarity, so badges dim accordingly
  • Species images: bird thumbnails from Wikipedia, downloaded in background as both 800px display thumbs AND full-resolution originals in MinIO. iNaturalist as opt-in fallback hub 0.29 for gallery extras when Wikipedia is exhausted — license-gated (default = commercial-safe cc0 / cc-by / cc-by-sa); admin can widen to include CC-BY-NC variants. Lightbox shows the license code as a small uppercase badge on the attribution pill

Detection & Timeline

Browse, verify, and explore your detections.

  • Detection list: filter by satellite, status, species, confidence — with server-side sorting and pagination. Species search works across all 38 languages
  • SpectrogramPlayer: click-to-seek, drag-to-select loop region, auto-boost (peak normalization), speed control (0.25x–2x), volume, download, buffering indicator
  • Timeline: one heatmap row per species across the selected window, with silent stretches marked (no audio, or a microphone below the silence floor)
  • Tentative detections: a detection counts once its species is heard the team's required number of times within its aggregation window on the same satellite, or once at high confidence (Team settings, Detection). Until then it is tentative: the Timeline draws it hatched, the By species view says how many it leaves out and links to them, and the badge on a tentative detection states the team's rule hub 0.77.3. Tentative detections are deleted after the team's retention time
  • One count everywhere hub 0.78.0: the Dashboard, Species, Analytics, Compare, the Map, the satellite list, the PDF report, exports and public pages count what the Detections page lists: promoted detections, one per call (a song stitched across several 3-second chunks counts once), in the categories the team shows
  • Community verification: vote Yes/No/Unsure with keyboard shortcuts, consensus threshold, accuracy stats. Optional time-boxed annotations hub 0.30: drag a rectangle on the spectrogram marking exactly when + at what frequency the species call is, then cast a vote — captures a labeled bounding-box dataset opportunistically (Merlin-style)
  • Calibrated confidence hub 0.30: per-(tenant, model) isotonic regression realigns BirdNET's raw confidence with observed confirmation rates. Detection rows show Conf 78% (62%) inline once a curve has been fit; refresh from the Models tab. Same approach Cornell's Johnston 2021 paper uses to fix probability bias in species-distribution-model output
  • Detection comments: inline expandable thread per detection
  • Shareable links: HMAC-signed public URLs with species image, translated names, stats (today + total + confidence), and lightbox (no login required). A link opens for 90 days hub 0.79.0, then says it has expired; links made before that version open until 90 days after the upgrade
  • Link previews: Open Graph meta tags served for all share URLs — rich previews on Discord, Slack, Telegram, WhatsApp, Twitter, Facebook
  • Link previews: Open Graph meta tags for rich previews on Discord, Slack, Telegram, WhatsApp, Twitter, Facebook

Analytics & Visualization

Understand your data at a glance.

  • Dashboard hub 0.45.0: period tiles with deltas (detections today vs yesterday, species this week vs prior week), a Highlights card (new species, rare flags, first-of-season returns, with photos), 7-day trend + hourly activity, recent detections (latest per species), and a platform-admin System health strip (pending chunks, queue, workers, storage)
  • Detection map: detection clusters by default (satellite markers and heatmap as opt-in layers), time range filter — species names in user's language. Custom date ranges hub 0.40.0: pick any start/end in a range calendar (also on Timeline and Compare); large windows render as zoom-refining grid cells whose counts are true totals, and the viewport auto-fits the selected window
  • Unified time selector hub 0.41.0: one control — rolling presets (last hour…year), calendar periods (hour…year, incl. quarter), and custom ranges down to the minute; rolling windows stay live, plus an "All time" choice that removes time filtering entirely
  • Shared time context hub 0.43.0: the selected window follows you across Timeline, Map, Compare, Analytics, and both Detections views — same control, same spot (right end of the toolbar) on every page. Filters and sorts on every page also survive refresh and navigation
  • Biodiversity indices: Shannon H', Simpson 1-D, Pielou's evenness — per satellite and global
  • Migration patterns: month-by-species heatmap showing seasonal presence
  • Weather correlation: Open-Meteo temperature + precipitation overlaid on detection activity
  • Location comparison: side-by-side satellite stats, Jaccard similarity, shared/unique species, hourly activity
  • Activity heatmaps: hour-by-day-of-week grids per satellite and globally

Smart Alerts

Get notified about what matters.

  • Three trigger types: detection (species, confidence, time window, rare/first-of-season/first-of-day), absence (no activity for N minutes), trend (diversity or count drop/increase)
  • Six notification channels: Email (SMTP), Slack (Block Kit), Telegram (Bot API), Google Chat (Cards v2), Discord (embeds), Webhook (X-BirdNet-Signature: sha256=… HMAC). Webhook signing secrets are AES-256-GCM-encrypted at rest (write-only through the API). The legacy standalone tenant-webhooks table was removed in favour of channels.
  • Fire modes: cooldown (re-trigger after N minutes), once_until_clear, and on_state_change. The seeded satellite-offline rule uses on_state_change so a down satellite alerts once per outage and auto-rearms on recovery, not every hour.
  • Once per species per day option — each species triggers at most one notification per rule per day
  • Hidden categories stay quiet hub 0.78.1: a detection in a category the team hides (engine noise, human voices, dogs...) reaches only the rules that name its species, a security alarm on dogs for instance; every other rule, built-in ones included, skips it, and its share link does not open. A rule's species list (comma-separated) and hours apply as typed in the editor, which the engine ignored before
  • Inline rename for rules and channels, duplicate rules (created disabled)
  • Alert messages in tenant's language — species names, labels, and rarity reasons translated based on tenant settings
  • Notification links point to public share pages (no login required for recipients)
  • Species images in notifications via Wikipedia direct URLs
  • Platform health banner: persistent warning when core services are down
  • Scheduled exports: cron-based automatic CSV/JSON/eBird/iNaturalist delivery via any channel

Data Export

Get your data out in standard formats.

  • CSV and JSON export with filters
  • PDF detection report hub 0.41.0: a print-ready report for any time window — summary stat cards, site map, activity chart with offline shading, and a species list with smart-cropped thumbnails, names in all your configured languages (with language shortcodes), confidence stats, and the towns/countries where each species was heard. A4 or US Letter (Account preference), English/French
  • Composable report sections hub 0.43.0: a PDF icon in the app header opens the report builder from any page — pick any mix of site map, activity chart, weekly pattern heatmap, weather correlation, biodiversity indices, species list, and species notes (Wikipedia intro + traits, hub 0.42.0); the report covers the shared time window and an optional satellite scope
  • Scheduled PDF reports hub 0.43.0: pdf as a scheduled-export format — a recurring report of everything since the last run, attached to email or delivered via webhook
  • eBird checklist generation
  • iNaturalist observation export
  • xeno-canto recording submission
  • Scheduled automatic exports with cron scheduling

Satellites & Fleet Management

Monitor and configure your entire network.

  • Fleet list: sortable, searchable, filterable — click to open dedicated satellite detail page
  • Satellite detail page: full-page view with stats cards (status, recording, version, location, CPU, storage, uptime, last seen), today's hourly activity chart, 24h schedule bar, filter stats, editable config, biodiversity, expected species (scrollable, with links to species profiles), live audio, actions
  • Telemetry: CPU, storage, battery, uptime, GPS, recording state — displayed as stat cards
  • Per-satellite config overrides: inline editor with lock toggle, customize/reset, override markers
  • Recording profile push: assign and push profiles via MQTT, integrated in schedule section
  • Remote update: trigger Pi satellite updates from the web UI via MQTT, with status feedback. Supports HTTPS token or SSH key authentication via .env
  • Audio device autodetection: automatically discovers the ALSA capture device when AUDIO_DEVICE is not set
  • Error state reporting: satellite reports "error" heartbeat after consecutive capture failures, recovers automatically
  • Filter stats: aggregate tracking of chunk filter results (processed/sent/rejected by reason) in satellite detail panel with pass rate bar
  • Per-session filter rate: timeline sessions show "X% sent" pill when significant filtering occurs
  • Satellite registration: register from web UI with complete .env generation
  • Schedule preview: resolved sun times and recording windows per satellite
  • Live audio: "Listen live" streams unfiltered real-time audio via hub-signaled MQTT — satellite sends all chunks (bypassing filter) while active, with 60s auto-stop timeout. Continuous playback with SpectrogramPlayer auto-chaining
  • Force update: always-visible button on satellite detail page (bypasses version check for dev/testing)
  • Disk management: WAV files deleted immediately on hub acknowledgment, periodic outbox DB purge

Android App

Turn any phone into a satellite. UI parity with the hub web (same Mantine v9 theme), bilingual EN/FR.

  • Continuous native capture phone 1.5.0: a native thread holds one AudioRecord open and slices gapless 3 s WAVs onto an on-disk queue; JS drains it when it gets CPU. Survives WebView throttling with the screen off, where the old per-chunk JS loop dropped audio. WebView.resumeTimers() on the AlarmManager keepalive un-throttles the drain.
  • Detection-first UI phone 1.6.0: three tabs. Live opens by default with an ambient listening pulse driven by the live chunk RMS, a hero card for the latest detection (species photo, confidence ring, rare / first-of-day badges), and today's species list grouped by count and last-heard. Monitor keeps the technical surface (icon stat cards, chunk audit, schedule, outbox storage, diagnostics + log viewer). Settings holds device behaviour toggles. The persistent notification mirrors the latest detection.
  • Live spectrogram phone 1.11.0: a scrolling spectrogram of the current recording on the Live tab, under the listening pulse. The native capture thread runs a 2048-point FFT (Hann window) per window and emits 64 frequency columns (0–12 kHz) on a spectrum Capacitor event; the canvas scrolls one column per window (~23/s) with an on-brand green ramp. Streaming is gated by startSpectrum/stopSpectrum and pauses on screen-off / backgrounding, so it never burns battery while the capture thread keeps running unattended.
  • On-satellite inference phone 1.12.0: decided hub > team > satellite since hub 0.64 (Hub settings → Defaults, Team settings → Satellites, each satellite page; a level can keep the one below from overriding it). When a phone is in device inference mode, chunks that pass the gates (every chunk when the audio filter is off) are classified locally by a bundled BirdNET v2.4 model (TensorFlow Lite). Only hits upload (audio + detections together on the inference-result channel); no-detection chunks drop on-device. Hits go through the same store-and-forward outbox, so they survive an offline hub. If the model can't load, the phone falls back to uploading audio for hub inference. The Live screen shows an "On-device AI" / "Hub AI" badge, a "Hub-managed" chip when config is locked, and a "Hearing a bird… / Heard: " pulse on the spectrogram. Device results honor the tenant's minimum confidence and the lat/lon/week range filter (phone 1.13.0, via a bundled BirdNET meta model), and bird names render in the tenant's primary language.
  • Design system phone 1.14.0: the satellite app has its own dark-first theme (emerald primary, caramel secondary, a 3-stop confidence scale), a sticky header with a dark/light toggle, a "Rings" listening pulse, an interactive OpenStreetMap site picker in Settings (zoom + drag the pin; placing it manually disables GPS), and Monitor/Settings laid out per the design handoff.
  • Offline-first recording phone 1.8.0: an unreachable hub never blocks the session. Capture starts before any network (mic live <1 s after launch, phone 1.10.0); chunks land in a persistent sql.js + Filesystem outbox first and upload when connectivity returns. The same three-pass purge as the Pi (age / hard cap / soft-cap acked-only) bounds storage locally. "Start recording when the app opens" toggle for unattended field installs.
  • Reliable upload pipeline (phone 1.10.x): paced one-chunk-at-a-time drain (waits for the broker PUBACK) so a backlog can't flood and kill the socket; a persistent MQTT session (clean: false) so the broker queues hub acks across brief reconnects instead of dropping them; client pings disabled (the throttled-WebView keepalive watchdog was self-killing healthy sockets), with half-open detection moved to PUBACK timeouts. The queue now drains fully to zero.
  • Notification lifecycle phone 1.8.0: the foreground service (and its notification, wake/wifi locks, mic) exists only while recording; pausing or swiping the app away releases everything cleanly.
  • Foreground service with AlarmManager keepalive, GPS gate (background fix, last-known coords until it lands), live audio streaming, hub filter config over MQTT, device-side recording-profile picker (hub-lockable).
  • MFA login phone 1.4.1: TOTP and backup-code prompts after the password step.
  • Satellite-scoped API key phone 1.4.0: registration mints a non-expiring satellite-scoped key, enforced server-side since hub 0.37.1 (a key can only touch its own satellite). The legacy session JWT is auto-exchanged on first launch after upgrade.
  • One-tap in-app updates phone 1.15.1: the hub announces each APK upload over MQTT, so running phones show the update banner within seconds (launch + 6 h poll as fallback); tapping it downloads in-app with progress, verifies the SHA-256 against the release record, and opens the system installer. "Later" skips that version; a forced release overrides the skip. APK download also surfaced on the hub's Satellites page for new-phone onboarding.

Multi-Tenancy & Auth

Multiple teams share infrastructure with full data isolation.

  • App-owned auth end to end hub 0.46: the application enforces all authentication and authorization itself; the reverse proxy terminates TLS and routes, nothing more
  • Self-serve teams hub 0.46: any signed-in user can create a team and becomes its owner, when the platform's tenant-creation toggle allows it
  • Row-level isolation via tenant_id on all data tables
  • Global users with per-tenant membership and roles (viewer < member < admin < owner)
  • Hub admin: cross-tenant access, hidden from tenant member lists
  • JWT cookie sessions (httpOnly, 7-day expiry) + Bearer API key auth
  • Invite system: single-use links with role assignment, 72h expiry
  • Login security: rate limiting (20/IP/15min), account lockout (5 failures), last login tracking
  • Tenant-scoping enforced on every UUID-addressed route hub 0.36.4: detail/audio/satellite/field-note reads and the create routes re-anchor authorization on the resource's own tenant (not the caller's resolved tenant), answering 404 so foreign UUIDs can't be probed. Vote/annotation identity comes from the session, not the request body.
  • Regression test suites lock this in: a tenant-isolation matrix and a security-hardening matrix run against the real app over an ephemeral Postgres.

Multi-Factor Authentication hub 0.27

Optional second factor for any user, mandatory for hub admins.

  • TOTP with QR-code enrollment — works with Google Authenticator, 1Password, Authy, etc. Secret AES-256-GCM-encrypted at rest.
  • WebAuthn passkeys — Touch ID, Windows Hello, YubiKey, etc. as an alternative second factor on devices that support them.
  • Backup codes — 10 single-use codes shown once at enrollment, copy/download/regenerate from /account.
  • Forced enrollment for hub admins before they can access the hub; CLI emergency reset escape hatch for the lost-device-and-codes case.
  • Remember this device for 30 days — opt-in cookie skips the second factor on the same browser; per-device list with revoke on /account.
  • Audit coverage: every MFA action (enable/disable, login_failed, passkey_added/removed, reset_by_admin, …) appears in the audit log.

User Management & Settings

Full control for admins and users.

  • Account page: edit profile, change password, delete account
  • User preferences: primary/secondary bird name languages, date/time format, voting mode
  • Platform users: search, block/unblock, reset password, add to tenant, edit, delete
  • Team settings: confidence thresholds, audio filter, pre-upload drops, upload queue, heartbeat, offline timeout, on-satellite inference, watchlist, language. Every setting in the registry follows the hub unless the team sets its own ("Hub default (30 s)"), and a padlock keeps the team's satellites on its value; a save sends only what changed hub 0.66. A screen that works on one team takes the team selector off "All teams" hub 0.66.1
  • Hub settings hub 0.29: six hash-deep-linked tabs, Access · Defaults · Images · Storage · Models · Mobile. Spotlight has one entry per tab, so "Wikimedia", "Retention", "APK", "iNaturalist" or "heartbeat" jump straight to the right tab.
    • Access: self-registration, team creation, public teams and public team pages
    • Defaults hub 0.67: every setting teams and satellites inherit (audio filter, drops, upload queue, heartbeat, on-satellite inference, detection thresholds, language), with a padlock per setting to keep every team on the hub's value
    • Images: cache stats with per-source breakdown (Wikimedia + iNaturalist; primary + extras), Wikimedia API config (contact email + OAuth token + test connection + hourly usage bar), iNaturalist toggle + license filter + per-minute usage bar
    • Storage: three cards. Storage overview (total bar with soft + hard cap markers, ✓/⚠ state hint, cap inputs). Audio (protected/unprotected split bar, 4 chunk stats, retention rules — days / keep-best / silent hours, schedule + run-now). Other caches (Images / Models / APKs proportional bar, gallery extras + APK keep-N).
    • Models: BirdNET classifier registry + Perch v2 embedding registry + Inference Mode (single / A/B compare with sample rate)
    • Mobile: APK upload + release list (force-update toggle, delete with storage cleanup)
    • Protected audio guarantee: keep-best + pinned chunks are NEVER auto-deleted, even at hard cap (v0.29 correctness pass)
  • Cross-tenant Members + Alerts inbox hub 0.29: hub admins on "Toutes les organisations" see Members and Alerts inbox spanning every tenant with an "Organisation" column; Members invite form gains a tenant Select picker. Tenant admins still see only their tenant
  • Audit log: 30+ action types covering login, registration, profile changes, satellite management, settings, votes, webhooks, cache operations — with real client IPs
  • GDPR rights hub 0.38.0: Export my data on the Account page downloads a JSON bundle of everything held about you (profile, memberships, votes, comments, annotations, field notes, MFA status, audit entries; no secrets). Account deletion cascade-wipes your child rows and anonymizes your audit-log entries in place (email + IP stripped, trail integrity kept). Since hub 0.76.0 it also takes the person out of the failed sign-ins typed with their address, the details of entries about them, the invitations sent to them and the configuration histories, and deletes their field-note photos. A privacy page (/privacy, hub 0.76.0) tells everyone what the hub keeps, why, who sees it, for how long and which outside services it uses (positions sent to eBird, Open-Meteo and Nominatim are rounded to about 1 km), with the operator's name and contact from Hub settings.
  • System monitoring: hub admin view of all running service instances (API, dispatcher, workers), infrastructure health, data summary, queue stats
  • Tenant ownership transfer

Internationalization (i18n)

The full interface in your language.

  • English and French translations with 1500+ keys covering every page (placeholder substitution via t(key, default, params))
  • Language switcher in sidebar
  • Locale-aware formatting: relative times, date/time, language names via Intl APIs
  • Singular/plural support via Intl.PluralRules (e.g. "1 detection" vs "5 detections")
  • Bird names in 38 languages via BirdNET label files (primary + secondary display)
  • Spotlight cross-language species search hub 0.29: every detected species in the active tenant appears in Spotlight (Cmd/Ctrl+K) with keywords[] carrying every translation already loaded for the user's primary + secondary languages, plus EN common name + Latin + species_code
  • Map popups and heatmap labels localized
  • Start-of-week preference (Monday/Sunday/Saturday) for heatmap grids

Per-Stream Versioning hub 0.28

Hub, Pi satellite, and mobile app version independently — backend churn no longer pings every satellite with phantom "update available" prompts.

  • Three SemVer streams: hub (vhub-X.Y.Z) / satellite (vsat-X.Y.Z) / mobile (vapk-X.Y.Z)
  • Per-scope bumping: pnpm version:bump --scope <hub|satellite|mobile>
  • Per-stream comparison on Satellites page: Pi satellites compare against the latest satellite release, mobile satellites against the latest mobile release. Hub-only bumps don't trigger a "needs update" badge anywhere
  • GET /api/system/versions: returns {hub, satellite, mobile} — latest target per stream
  • Pi update flow: birdnet-update.sh checks out the latest vsat-* tag; hub-triggered updates work unchanged
  • Mobile versionCode: deterministically derived from SemVer (major*1_000_000 + minor*1_000 + patch) — every released APK is now a real version bump, no more +b<timestamp> suffix

Infrastructure

  • Docker Compose: 8-container stack (9 with split mode) behind Traefik reverse proxy
  • Hub split mode: stateless API (scalable) + single-instance dispatcher
  • Three public names: web + API, MQTT, documentation site; MinIO stays on the internal network hub 0.78.2. A move to new names keeps the previous ones working meanwhile (docker-compose.previous-names.yml)
  • MQTT over WSS (port 443, no extra firewall ports)
  • Service registry: Redis-based auto-discovery with System monitoring page
  • 80 database migrations, run automatically on hub startup
  • Deep health checks hub 0.37.0: /api/health is a cheap liveness stub; /api/health/ready probes Postgres, Redis, MinIO, and MQTT with per-check timeouts and returns 503 when any is down. Docker healthchecks poll it (api), with heartbeat-file probes for the dispatcher and Python workers.
  • Inference worker hardening hub 0.37.0: pooled Postgres connections, exponential retry backoff so a poison job can't tight-loop, LRU-capped range-filter cache, pre-download size guard, persistent model cache volume, cpu/memory limits. Plus the self-healing exits hub 0.32.8 on Redis loss or a stalled processed-counter.
  • Idempotent re-ingest hub 0.37.4: a re-sent chunk (lost ack) is acked without re-running inference. Detections are timestamped by recording time, not analysis time hub 0.37.5, so store-and-forward backlogs land in the right place on the timeline.
  • Bounded job retention: finished BullMQ jobs are trimmed (24 h completed / 7 d failed) so Redis doesn't grow without limit.
  • Test suites: hub (routes + tenant isolation + security hardening over an ephemeral Postgres), Pi and mobile outbox lifecycle + purge.
  • Backup sidecar hub 0.38.2: nightly rotated pg_dump + incremental MinIO mirror + Mosquitto credential copy, local then off-site via rclone. Restore script + runbook (deployment/backup-restore.md); Redis intentionally excluded.
  • Versioned documentation site with version switcher