API Reference
The Hub API is a Fastify HTTP server. All endpoints are under /api/.
Authentication
| Method | Description |
|---|---|
| JWT Cookie | Set by POST /api/auth/login, sent automatically by browsers |
| Bearer Token | Authorization: Bearer <token> for mobile apps and API clients |
Both resolve to a user identity with tenant role. Platform admins have cross-tenant access.
Login security:
- Rate limiting: max 20 attempts per IP per 15 minutes (429)
- Account lockout: 5 failed attempts locks for 15 minutes (423)
- Last login timestamp + IP recorded on success
- Platform admin status resolved from DB on every request (not from stale JWT)
Auth
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/register |
Public* | Register user (with optional invite token) |
| POST | /auth/login |
Public | Login, returns JWT cookie (+ token if ?returnToken=true) |
| POST | /auth/logout |
Authenticated | Clear session |
| GET | /auth/me |
Authenticated | Current user, tenants, preferences |
| PUT | /auth/me/preferences |
Authenticated | Update language, date/time format, voting mode preferences |
| PUT | /auth/me/profile |
Authenticated | Update name and/or email |
| PUT | /auth/me/password |
Authenticated | Change password (requires current) |
| DELETE | /auth/me |
Authenticated | Delete own account |
| POST | /auth/invites |
Admin+ | Generate invite link (tenant + role) |
| GET | /auth/invites |
Admin+ | List pending invites |
| DELETE | /auth/invites/:id |
Admin+ | Revoke invite |
| GET | /auth/members |
Admin+ | List tenant members (excludes platform admins) |
| POST | /auth/create-tenant |
Logged in* | Create new tenant |
*Gated by platform settings (allow_self_registration, allow_tenant_creation)
Detections
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /detections |
Viewer+ | List detections (filterable) |
| GET | /detections/:id |
Viewer+ | Detection detail with storage key |
| GET | /detections/votes |
Viewer+ | Get user's votes for detection IDs |
| POST | /detections/:id/verify |
Member+ | Submit verification vote |
| GET | /detections/:id/comments |
Viewer+ | List comments on a detection |
| POST | /detections/:id/comments |
Member+ | Add a comment |
| DELETE | /detections/:id/comments/:commentId |
Member+ | Delete own comment (admin can delete any) |
| GET | /detections/:id/share |
Viewer+ | Generate HMAC share token |
| GET | /share/detection/:id/:token |
Public | View shared detection (no auth) |
| GET | /share/audio/:id/:token |
Public | Stream audio for shared detection (no auth) |
Query params for GET /detections:
tenantId— filter by tenantsatelliteId— filter by satellitespecies— filter by species codespeciesSearch— search by common or scientific name (ILIKE)minConfidence— minimum confidence thresholdverificationStatus—pending_review,confirmed,rejected,unverifiedsortBy— sort field:date(default),confidence,speciessortDir— sort direction:ascordesc(default)limit,offset— pagination
Response: { detections: [...], total: number }. Each detection includes an is_first_of_season boolean flag indicating whether it was the first observation of that species in the current season for the tenant.
Sessions
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /sessions |
Viewer+ | List recording sessions |
| GET | /sessions/timeline |
Viewer+ | Chunks + detections for a time range |
Query params for GET /sessions:
tenantId— filter by tenantsatelliteId— filter by satellitefilter— session type:with-detections,empty, or omit for allsortBy— sort field:date(default),duration,detections,species,chunkssortDir— sort direction:ascordesc(default)limit,offset— pagination
Response: { sessions: [...], total: number }
Query params for GET /sessions/timeline:
tenantId— filter by tenantsatelliteId— filter by satellitefrom,to— ISO 8601 time range boundaries
Sessions are groups of consecutive audio chunks separated by a gap of 5 minutes or more. Each session includes its start/end time, chunk count, and detection summary.
Satellites
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /satellites |
Viewer+ | List satellites (filterable, sortable, paginated) |
| GET | /satellites/:id |
Viewer+ | Satellite detail |
| POST | /satellites |
Admin+ | Register satellite (returns MQTT credentials) |
| PATCH | /satellites/:id |
Member+ | Update satellite (rename) |
| POST | /satellites/:id/profile |
Admin+ | Push recording profile via MQTT |
| POST | /satellites/:id/update |
Admin+ | Request remote update via MQTT |
| GET | /satellites/:id/config |
Viewer+ | Get effective config (overrides merged with tenant defaults) |
| PUT | /satellites/:id/config |
Member+ | Update config overrides and/or lock state |
| GET | /satellites/:id/schedule |
Viewer+ | Resolved recording schedule with sun times |
| DELETE | /satellites/:id |
Admin+ | Delete satellite (revokes MQTT) |
PUT /satellites/:id/config body:
configLocked— boolean (admin-only: lock config so device cannot change it)overrides— object with nullable fields:filter_enabled,filter_min_rms,yamnet_min_bird_prob,outbox_soft_size_mb,outbox_hard_size_mb,outbox_max_age_hours,heartbeat_interval_sec,drop_amphibian_at_satellite,drop_insect_at_satellite,drop_anthropogenic_at_satellite,drop_human_voice_at_satellite,drop_other_animal_at_satellite. Set any tonullto inherit the tenant default.
Config resolution: satellite_override[field] ?? tenant_settings[field] ?? default
Audio
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /audio/:chunkId |
Viewer+ | Stream audio WAV from MinIO |
Alerts
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /alerts |
Viewer+ | List alerts (?acknowledged=true/false) |
| POST | /alerts/:id/acknowledge |
Viewer+ | Dismiss alert |
Export
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /export/detections |
Member+ | Export CSV or JSON (?format=csv/json) |
| GET | /export/ebird |
Member+ | eBird checklist format |
| GET | /export/inaturalist |
Member+ | iNaturalist observation format |
| GET | /export/xenocanto |
Member+ | xeno-canto submission format |
Stats
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /stats/dashboard |
Viewer+ | Species count, detection count, chunk count |
| GET | /stats/trends |
Viewer+ | Daily detection trends (?days=30) |
| GET | /stats/top-species |
Viewer+ | Top species ranking |
| GET | /stats/activity |
Viewer+ | Hourly detection activity |
| GET | /stats/verification |
Viewer+ | Verification progress counts |
| GET | /stats/leaderboard |
Viewer+ | Verification contributor ranking |
| GET | /stats/species |
Viewer+ | Species catalog with rich per-species stats |
| GET | /stats/species/:code |
Viewer+ | Species profile (stats, trend, hourly, satellites, confidence, recent) |
| GET | /stats/compare |
Viewer+ | Side-by-side satellite comparison (species overlap, Jaccard index) |
| GET | /stats/weather-correlation |
Viewer+ | Weather + detection correlation (Open-Meteo) |
| GET | /stats/migration |
Viewer+ | Migration patterns (monthly species presence) |
| GET | /stats/biodiversity |
Viewer+ | Biodiversity indices (Shannon, Simpson, evenness per satellite) |
| GET | /stats/expected-species |
Viewer+ | Expected species at location (eBird integration) |
| GET | /stats/activity-heatmap |
Viewer+ | Hour×day heatmap (optional satelliteId filter) |
| GET | /stats/satellite-activity |
Viewer+ | Peak hours per satellite |
| GET | /stats/map |
Viewer+ | Satellite positions with detection summaries |
| GET | /stats/map/detections |
Viewer+ | Per-detection GPS points (filterable by days) |
| GET | /stats/species-accuracy |
Viewer+ | Per-species verification accuracy stats |
Species
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /species/languages |
Public | Available translation languages |
| GET | /species/translate |
Viewer+ | Translate single species |
| POST | /species/translate |
Viewer+ | Bulk translate species names |
| POST | /species/images |
Viewer+ | Get image statuses (queues missing) |
| GET | /species/image/proxy |
Viewer+ | Serve species image from MinIO |
| GET | /species/image-cache/stats |
Platform Admin | Image cache statistics |
| POST | /species/image-cache/clear |
Platform Admin | Clear entire image cache |
| POST | /species/image-cache/retry |
Platform Admin | Re-queue failed downloads |
Webhooks
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /webhooks |
Admin+ | List webhooks for tenant |
| POST | /webhooks |
Admin+ | Create webhook |
| DELETE | /webhooks/:id |
Admin+ | Delete webhook |
| POST | /webhooks/:id/test |
Admin+ | Send test payload |
Webhook payload: Detection details with HMAC signature in X-Webhook-Signature header.
Event types: detection.new, detection.rare, satellite.offline
Alert Rules
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /alert-rules/rules |
Viewer+ | List alert rules for tenant |
| POST | /alert-rules/rules |
Admin+ | Create alert rule |
| PUT | /alert-rules/rules/:id |
Admin+ | Update alert rule |
| DELETE | /alert-rules/rules/:id |
Admin+ | Delete alert rule |
Trigger types: detection (on new detection matching conditions), absence (no activity for N minutes), trend (species diversity or detection count drop/increase)
Alert Channels
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /alert-rules/channels |
Viewer+ | List notification channels for tenant |
| POST | /alert-rules/channels |
Admin+ | Create notification channel |
| PUT | /alert-rules/channels/:id |
Admin+ | Update channel |
| DELETE | /alert-rules/channels/:id |
Admin+ | Delete channel |
| POST | /alert-rules/channels/:id/test |
Admin+ | Send test message |
Channel types: email (SMTP), slack (Block Kit), telegram (Bot API), google_chat (Cards v2), discord (rich embeds), webhook (generic with HMAC)
Scheduled Exports
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /exports/scheduled |
Viewer+ | List scheduled exports for tenant |
| POST | /exports/scheduled |
Admin+ | Create scheduled export |
| PUT | /exports/scheduled/:id |
Admin+ | Update scheduled export |
| DELETE | /exports/scheduled/:id |
Admin+ | Delete scheduled export |
Formats: csv, json, ebird, inaturalist
Schedule: cron expression (e.g. 0 6 * * 1 for weekly Monday 6am)
Delivery: via alert channel (email attachment or webhook POST)
Workers
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /workers |
Admin+ | Queue stats + connected workers (IP-based dedup) |
| GET | /workers/jobs |
Admin+ | Recent completed jobs |
Settings
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /settings/tenant |
Viewer+ | Tenant settings |
| PUT | /settings/tenant |
Admin+ | Update tenant settings |
| GET | /settings/platform |
Platform Admin | Platform settings |
| PUT | /settings/platform |
Platform Admin | Update platform settings |
Tenants
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /tenants |
Authenticated | List tenants |
Admin (Platform Admin only)
| Method | Path | Description |
|---|---|---|
| GET | /admin/users |
List all users (search, status, tenant filters) |
| GET | /admin/users/:id |
User detail |
| PUT | /admin/users/:id |
Update user (name, email, admin flag) |
| POST | /admin/users/:id/block |
Block/unblock user |
| POST | /admin/users/:id/reset-password |
Reset password |
| DELETE | /admin/users/:id |
Delete user |
| POST | /admin/users/:id/tenants |
Add user to tenant (409 if already member) |
| DELETE | /admin/users/:id/tenants/:tid |
Remove from tenant |
| PUT | /admin/members/:id/role |
Change member role |
| DELETE | /admin/members/:id |
Remove member from tenant |
| POST | /admin/members/:id/transfer-ownership |
Transfer tenant ownership |
| GET | /admin/audit-log |
Audit log (paginated) |
User list query params: ?search=, ?status=active|blocked, ?tenantId=
Audit Log
All audit log entries include the real client IP address (via X-Forwarded-For, with trustProxy enabled on Fastify).
User actions:
user.register— new account createduser.login— successful loginuser.login_failed— failed login attempt (reason:rate_limited,locked,invalid_credentials,blocked)user.logout— session endeduser.profile_updated— name or email changeduser.password_changed— password updateduser.preferences_updated— language or date format preferences changeduser.account_deleted— user deleted their own account
Admin actions:
admin.user_edited— admin updated user detailsadmin.user_blocked— admin blocked a useradmin.user_unblocked— admin unblocked a useradmin.user_deleted— admin deleted a useradmin.password_reset— admin reset a user's passwordadmin.user_added_to_tenant— admin added a user to a tenantadmin.user_removed_from_tenant— admin removed a user from a tenantadmin.member_role_changed— admin changed a member's roleadmin.member_removed— admin removed a memberadmin.invite_created— admin generated an invite linkadmin.invite_revoked— admin revoked an inviteadmin.tenant_created— new tenant createdadmin.tenant_settings_updated— tenant settings changedadmin.platform_settings_updated— platform settings changedadmin.ownership_transferred— tenant ownership transferred to another useradmin.satellite_deleted— satellite removedadmin.satellite_renamed— satellite renamedadmin.satellite_profile_changed— satellite recording profile changedadmin.webhook_created— webhook createdadmin.webhook_deleted— webhook deletedadmin.image_cache_cleared— species image cache cleared
Detection actions:
detection.voted— user submitted a verification vote
WebSocket
Connect to /ws/events?tenantId=<id> for real-time events.
Event types:
detection— new bird detected (species, confidence, satellite, rare flag)alert— system alert (rare species, satellite offline, etc.)
Toast format: Rare species notifications show translated names (primary + secondary languages).
System
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /system/status |
Platform Admin | Full platform status: all service instances, infrastructure, data summary |
Response includes:
- Services: each registered instance (API, dispatcher, web, workers, satellites) with status, version, uptime, memory usage, IP address, and service-specific details (e.g., per-worker job stats: processed, succeeded, failed, avg duration)
- Infrastructure: PostgreSQL, Redis, MQTT, MinIO connectivity and status
- Data summary: detection counts, inference queue stats, species image totals, tenant and user counts
Service instances are discovered via Redis service registry (birdnet:registry:{service}:{instanceId}, 30s TTL).
Health
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
Public | Returns { status, version, timestamp } |