OPERATIONS GUIDE · API V1

Valkyris documentation

Everything needed to install the server, prepare cameras and lighting, connect Android and integrate the API.

Continuously updated documentation OpenAPI 3.1
01

Get started

The installer downloads release artifacts, generates local secrets and a TLS certificate, prepares Compose, then starts Valkyris and MediaMTX.

curl -fsSL https://valkyris.vercel.app/install.sh | sh

Requirements

  • ✓Linux on amd64 or arm64
  • ✓Docker Engine with Docker Compose v2
  • ✓Android 8.0 (SDK 26) or later
  • ✓ONVIF Profile S camera with RTSP
  • ✓Server, phone and camera reachable over LAN or VPN

Keep ONVIF and RTSP ports reachable only on the private network. For remote access, connect the phone through a VPN; never publish the camera to the internet.

02

Architecture

CameraONVIF · RTSP
→
MediaMTXWebRTC · buffer · Opus
→
Go + SQLiterules · events
→
AndroidCompose · push

The backend is the security boundary. The app never receives camera passwords or local adapter secrets. Cameras pass through MediaMTX; lighting uses a vendor-neutral driver boundary. Credentials use AES-256-GCM encryption; tokens are stored only as hashes.

03

Prepare a camera

  1. 1
    Connect to Wi-Fi

    For a TC40, use Tapo once to complete initial provisioning.

  2. 2
    Create the Camera Account

    Use dedicated credentials in advanced settings. This is not your TP-Link account password.

  3. 3
    Check the local network

    The server must reach the camera ONVIF and RTSP ports on the private network.

  4. 4
    Add it in the app

    Enter name, icon, IP, username and password. The primary Tapo RTSP URL is built automatically; use the advanced field only to override it.

The camera is persisted immediately. Its screen follows queued, probing, stream, ready or failed in real time, and the last sanitized error remains stored for diagnosis.

Smart lighting

The common power, brightness, white-temperature and RGB model remains available in the app and dashboard, but the core includes no proprietary manufacturer-account integration. Standard local adapters can be added behind the driver boundary without redesigning the interfaces.

Existing records are not deleted automatically during this transition. An administrator can still remove them from the app.

04

Android

Install the APK from the latest release. On first access, enter the HTTPS URL through which the phone reaches the server and create the personal username and password. The first device becomes administrator.

Other phones

In Settings, the administrator creates a temporary, single-use invitation. The QR is assembled inside the app using the known server URL; the backend has no public pairing page.

Native notifications

The APK uses Firebase Cloud Messaging directly and requires no companion app. After login, this phone token is registered automatically; Android receives a minimal encrypted payload and Valkyris creates the native notification or alarm.

  1. 1
    Configure the APK

    Add com.ferforastieri.valkyris to the Firebase project, download google-services.json and store its base64 in the FIREBASE_ANDROID_CONFIG_BASE64 GitHub secret.

  2. 2
    Configure the server

    Generate a JSON key under Project settings → Service accounts. In the app alert sheet, upload this JSON: it is validated and encrypted in your server SQLite database.

  3. 3
    Grant permission

    Open the APK from the new release, sign in and allow notifications. FCM registration happens automatically.

The API accepts the same setup for administrative integrations at PUT /api/v1/settings/push with serviceAccountBase64. The account is never returned by the API.

Web dashboard on your server

Open /app/ at your installation HTTPS URL and use the personal username and password. The dashboard displays live cameras, controls registered bulbs, and shows events, family, routes and server information. Device registration and setup remain on Android.

Sessions use a Secure, HttpOnly, SameSite=Strict cookie, valid for 30 days and renewed during use. Logout or password changes revoke sessions. The dashboard ships in the Docker image and requires neither Node nor Vercel deployment. Data refreshes every 15 seconds while the tab is visible.

WebRTC · Cloudflare Tunnel

Android and the dashboard use the same WHEP endpoints. The tunnel carries HTTPS and signaling, but audio and video must reach MediaMTX through ICE. Allow 8189 UDP/TCP on the private network and set VALKYRIS_WEBRTC_HOSTS to reachable LAN/VPN addresses. The tunnel hostname is not a media address. STUN can assist direct connections; restrictive NAT requires VPN, a public media route or configured TURN.

The player does not replace WebRTC with a snapshot or HLS. An eight-second ICE wait does not abort Android negotiation when usable candidates already exist. HTTP 201 during signaling does not prove playback: verify received frames.

Location, events and motion

Android combines location sources through Fused Location Provider. Entering or leaving requires three readings spanning at least two minutes, beyond the accuracy margin. An uncertain reading or return cancels confirmation. The server requests samples while confirmation is pending without filling history with stationary points.

Alerts name the person and area and are not sent to the moving user. History shows numbered points, time and accuracy; maps zoom in on nearby people. Accuracy is estimated, and missing signal or connectivity may delay alerts. Install the current APK on every phone.

Camera rules support persistent motion in an image region, duration, sensitivity and days/time windows, including 22:00–06:00. Keep the camera fixed and redraw the region after PTZ movement. Analysis detects regional motion, not baby identity, posture, breathing or medical danger; it does not replace supervision.

Events are grouped into location, audio and camera categories. Details show person/area and position when available; clips report processing, ready or unavailable states.

05

Configuration

VALKYRIS_WEBRTC_HOSTSIP LAN/VPNAdvertised media addresses, comma separated.
VALKYRIS_WEB_DIR/opt/valkyris/webStatic dashboard build served by the backend.
VALKYRIS_LISTEN:8443Internal HTTPS listen address.
VALKYRIS_DATA_DIR/dataPersistent data volume.
VALKYRIS_DATABASE/data/valkyris.dbSQLite.
VALKYRIS_TLS_CERT/data/tls/server.crtTLS certificate.
VALKYRIS_TLS_KEY/data/tls/server.keyTLS key.
VALKYRIS_MASTER_KEY_FILE/data/secrets/master.keyEncryption master key.
VALKYRIS_MEDIA_APIhttp://mediamtx:9997Internal MediaMTX API.
VALKYRIS_MEDIA_RTSPrtsp://mediamtx:8554Internal stream for monitoring and detectors.
VALKYRIS_MEDIA_WEBRTChttp://mediamtx:8889Internal WHEP origin; WebRTC media goes directly to the phone after ICE.
VALKYRIS_MEDIA_PLAYBACKhttp://mediamtx:9996Internal API used to generate recent clips.
VALKYRIS_RELEASE_APIapi.github.com/…/releases/latestStable release lookup.
VALKYRIS_FIREBASE_CREDENTIALS_FILE/data/secrets/firebase-service-account.jsonLegacy alternative: FCM service-account file. The app uses encrypted SQLite storage.

mediamtx.yml must exist as a file before Compose starts.

06

HTTP API

Android and integrations use bearer tokens; the dashboard uses an HttpOnly cookie and X-Valkyris-Viewer: 1. Public routes require no session. The base path is /api/v1. JSON responses use the success, message and data envelope; errors use success, message and error.

curl -k https://SEU_SERVIDOR:8443/api/v1/cameras \ -H 'Authorization: Bearer SEU_TOKEN'

Endpoint reference

OpenAPI YAML
GET/healthPublic

Server health.

GET/openapi.yamlPublic

OpenAPI 3.1 contract served by the backend.

GET/api/v1/auth/statusPublic

Reports whether the first administrator exists.

POST/api/v1/admin/bootstrapPublic

Creates the personal username and password and first administrator once.

POST/api/v1/loginPublic

Exchanges the personal username and password for a device token.

POST/api/v1/pairPublic

Consumes a single-use invitation.

POST/api/v1/pairing-sessionsAdmin

Creates a temporary invitation for another phone.

GET · POST/api/v1/camerasAuthenticated

Lists cameras or starts asynchronous setup.

PUT · DELETE/api/v1/cameras/{id}Authenticated

Edits or deletes a camera; connection changes restart its setup.

GET/api/v1/camera-operations/{id}Authenticated

Tracks ONVIF discovery and media setup.

POST/api/v1/cameras/{id}/ptzAuthenticated

Moves, stops or zooms when supported.

GET/api/v1/lightsAuthenticated

Lists lighting exposed by the installed local adapter.

GET · DELETE/api/v1/lights/{id}Authenticated / Admin to remove

Reads or removes a light.

PUT/api/v1/lights/{id}/stateAuthenticated

Turns a bulb on or off and changes brightness, white temperature or color.

GET/api/v1/cameras/{id}/snapshotAuthenticated

Returns a JPEG frame from the current local stream.

GET/api/v1/cameras/{id}/recordingAuthenticated

Downloads the latest 10 seconds from the rolling buffer as MP4.

POST · PATCH · DELETE/api/v1/cameras/{id}/live/webrtc/whepAuthenticated

Negotiates and closes the authenticated WebRTC stream.

GET/api/v1/detectorsAuthenticated

Lists normalized detector types.

GET · POST/api/v1/rulesAuthenticated

Lists or creates automation rules.

PUT · DELETE/api/v1/rules/{id}Authenticated

Edits or deletes a rule.

GET/api/v1/usersAuthenticated

Lists family profiles linked to paired devices.

GET/api/v1/users/{id}/historyAuthenticated

Returns a user location history.

POST/api/v1/me/locationAuthenticated

Records the location of the user linked to the current device.

GET · POST/api/v1/placesAuthenticated / Admin to change

Lists areas or creates an alert area.

PUT · DELETE/api/v1/places/{id}Admin

Edits or removes an area.

GET · PUT/api/v1/meAuthenticated

Reads or updates this device owner profile.

POST/api/v1/me/passwordUser

Changes the personal password and revokes other sessions.

GET · DELETE/api/v1/viewer-sessionBrowser session

Restores or revokes the dashboard session.

GET/api/v1/eventsAuthenticated

Lists recent events.

GET/api/v1/events/{id}Authenticated

Returns event metadata.

POST/api/v1/events/{id}/acknowledgeAuthenticated

Acknowledges the event and stops its alarm.

POST/api/v1/events/acknowledge-allAuthenticated

Acknowledges all pending events.

GET/api/v1/events/{id}/snapshotAuthenticated

Returns the event JPEG.

GET/api/v1/events/{id}/clipAuthenticated

Streams the event MP4 clip.

POST/api/v1/devices/pushAuthenticated

Registers the FCM token and encrypted payload secret.

GET · PUT/api/v1/settings/pushAdmin to change

Reads FCM setup or stores a Base64 service account encrypted in SQLite.

GET · PUT/api/v1/settings/retentionAdmin to change

Reads or changes retention limits.

POST/api/v1/detectionsAuthenticated

Accepts normalized detections from local integrations.

GET/api/v1/system/updateAuthenticated

Checks release metadata and the APK link without executing updates.

GET/api/v1/realtimeAuthenticated

WebSocket for camera, lighting and event changes.

07

Updates

When a newer stable release exists, mobile and web show a toast without updating automatically. In app settings, the user can start downloading the signed APK from GitHub; Android requests installation confirmation.

SQLite, clips and snapshots remain in the valkyris-data volume. The API only checks releases; it cannot execute server updates.

In the 2.x series the API remains /api/v1 and migration preserves data. Rerun the installer on the host to update the backend, web, Compose and MediaMTX. Install the APK on each phone with Android confirmation. Release checks are cached for up to 15 minutes.

08

Security and backup

Per-minute API limits: 3000 global and 1200 per address; authentication has 30 global and 10 per address; snapshots/recordings have 60 per session/address, and WHEP creation has 12. A 429 response includes Retry-After. The backend does not trust client X-Forwarded-For or CF-Connecting-IP headers; proxy users share its budget. Counters are process-local.

For backups, use the SQLite backup API or stop the stack before copying the volume. Preserve the database, WAL when applicable, master key, certificates, media, .env, Compose and mediamtx.yml. Copying only a running .db can produce an incomplete backup.

  • Do not forward ports 554, 2020, 8888 or 9997 on the router.
  • Use trusted TLS through a reverse proxy or install the private CA on the phone.
  • Use Tailscale, WireGuard or another VPN away from home.
  • Back up the valkyris-data volume and .env together with restricted access.
09

Diagnostics

docker compose ps docker compose logs --tail=200 valkyris docker compose logs --tail=200 mediamtx curl -k https://localhost:8443/health
setup failed

Open the camera in the app: persisted setup state and error show whether ONVIF or stream setup failed.

EOFException

First verify MediaMTX health and that mediamtx.yml is mounted as a file.

No preview

Check snapshot, selected ONVIF profile and RTSP connectivity between host and camera.