Documentation
Send frames to a camera
One endpoint, one device key. Post an image whenever you like — every 10 seconds is typical — and the public live view picks it up on its next refresh.
Endpoint
POST https://your-site.lovable.app/api/public/ingestAuthentication — any of these
x-device-key: KEYheaderAuthorization: Bearer KEYheaderAuthorization: Basic(key as the password)?key=KEYin the URL — for devices that only let you set a URL
Body — any of these
- Raw binary image (JPEG, PNG, WebP)
multipart/form-datawith animage/filefield- JSON
{ "image": "<base64 or data URL>" } - Optional
captured_at(ISO 8601) to backfill a timestamp
Recipes
Replace $AUVI_KEY with the device key for your camera.
1. Test the key first
A GET returns the camera the key belongs to and how many frames it already has. Run this before wiring a schedule.
curl "https://your-site.lovable.app/api/public/ingest" -H "x-device-key: $AUVI_KEY"
# {"ok":true,"camera":{"slug":"riverside-north","name":"…","intervalSeconds":10},"frames":0}2. curl — raw image body
The simplest possible upload. Works from any shell, cron job, or CI runner.
curl -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "x-device-key: $AUVI_KEY" \
-H "content-type: image/jpeg" \
--data-binary @frame.jpg3. curl — multipart form upload
Use this when your tool speaks form uploads rather than raw bodies.
curl -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "x-device-key: $AUVI_KEY" \
-F "image=@frame.jpg" \
-F "captured_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"4. Every 10 seconds from a Linux box
A shell loop is more reliable than cron for sub-minute cadence. Run it under systemd or supervisor so it restarts on reboot.
#!/usr/bin/env bash
export AUVI_KEY="paste-your-device-key"
while true; do
# grab a still from a USB camera (fswebcam) or an RTSP stream (ffmpeg)
fswebcam -r 1920x1080 --no-banner /tmp/frame.jpg
curl -sf -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "x-device-key: $AUVI_KEY" \
-H "content-type: image/jpeg" \
--data-binary @/tmp/frame.jpg > /dev/null || echo "upload failed"
sleep 10
done5. RTSP / IP camera via ffmpeg — one frame
Pull a still from any RTSP or HTTP camera and pipe it straight up — no local storage needed.
ffmpeg -rtsp_transport tcp -i "rtsp://user:pass@10.0.0.42:554/stream1" \
-frames:v 1 -q:v 3 -f image2 - 2>/dev/null |
curl -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "x-device-key: $AUVI_KEY" \
-H "content-type: image/jpeg" \
--data-binary @-6. ffmpeg RTSP — a frame every 10 seconds
A supervised loop: pull one still per cycle, post it, and sleep only for the time the capture didn't use, so cadence stays on 10s even when the camera is slow to answer. Download auvi-rtsp-ingest.sh below for the hardened version with key checks, 429 backoff, and every auth method.
#!/usr/bin/env bash
set -uo pipefail
export AUVI_KEY="paste-your-device-key"
RTSP="rtsp://user:pass@10.0.0.42:554/stream1"
FRAME=/tmp/auvi-frame.jpg
while true; do
started=$(date +%s)
captured_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
ffmpeg -hide_banner -loglevel error -y \
-rtsp_transport tcp -i "$RTSP" \
-frames:v 1 -q:v 3 -f image2 "$FRAME" || { echo "capture failed"; sleep 5; continue; }
curl -sf -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "x-device-key: $AUVI_KEY" \
-H "content-type: image/jpeg" \
-H "x-captured-at: $captured_at" \
--max-time 30 \
--data-binary @"$FRAME" > /dev/null || echo "upload failed"
sleep $(( 10 - ($(date +%s) - started) > 0 ? 10 - ($(date +%s) - started) : 1 ))
done7. Snapshot URL → Auvi, in every auth method
Most IP cameras expose a still at an HTTP snapshot path. Pipe it straight into Auvi — same command, four ways to authenticate. Pick whichever your network or device allows.
SNAP="http://user:pass@10.0.0.42/cgi-bin/snapshot.cgi" # Axis: /axis-cgi/jpg/image.cgi
AUVI_KEY="paste-your-device-key"
# a) Header — the default, works everywhere
curl -sf "$SNAP" | curl -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "x-device-key: $AUVI_KEY" \
-H "content-type: image/jpeg" --data-binary @-
# b) Bearer token
curl -sf "$SNAP" | curl -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "authorization: Bearer $AUVI_KEY" \
-H "content-type: image/jpeg" --data-binary @-
# c) Basic auth — key as the password, any username
curl -sf "$SNAP" | curl -X POST "https://your-site.lovable.app/api/public/ingest" \
--user "auvi:$AUVI_KEY" \
-H "content-type: image/jpeg" --data-binary @-
# d) Query string — for devices that only let you paste a URL
curl -sf "$SNAP" | curl -X POST "https://your-site.lovable.app/api/public/ingest?key=$AUVI_KEY" \
-H "content-type: image/jpeg" --data-binary @-
# Add "?captured_at=2026-08-29T10:00:00Z" or an "x-captured-at" header
# to backfill the capture time on a raw upload.8. Cameras that only accept a URL (Axis, Hikvision, Reolink, trail cams)
Many devices have an HTTP/FTP-to-HTTP push setting where you can only paste a destination URL. Put the key in the query string and point the device's periodic upload at it.
https://your-site.lovable.app/api/public/ingest?key=YOUR_DEVICE_KEY
# Device settings:
# Method: POST
# Content type: image/jpeg (or multipart form with field name "image")
# Interval: 10 seconds9. Python
Good for Raspberry Pi / Jetson deployments using picamera2 or OpenCV.
import os, time, requests
ENDPOINT = "https://your-site.lovable.app/api/public/ingest"
KEY = os.environ["AUVI_KEY"]
while True:
with open("/tmp/frame.jpg", "rb") as f:
r = requests.post(
ENDPOINT,
headers={"x-device-key": KEY, "content-type": "image/jpeg"},
data=f.read(),
timeout=20,
)
print(r.status_code, r.text)
time.sleep(10)10. Node.js
Uses fetch, so no dependencies on Node 18+.
import { readFile } from "node:fs/promises";
const ENDPOINT = "https://your-site.lovable.app/api/public/ingest";
const KEY = process.env.AUVI_KEY;
setInterval(async () => {
const body = await readFile("./frame.jpg");
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "x-device-key": KEY, "content-type": "image/jpeg" },
body,
});
console.log(await res.json());
}, 10_000);11. JSON / base64 (Zapier, Make, ESP32-CAM, webhooks)
For platforms that can only send JSON. A data URL works too.
curl -X POST "https://your-site.lovable.app/api/public/ingest" \
-H "authorization: Bearer $AUVI_KEY" \
-H "content-type: application/json" \
-d "{\"image\":\"$(base64 -w0 frame.jpg)\",\"captured_at\":\"2026-08-29T10:00:00Z\"}"Reference clients
Complete, dependency-free clients that cover every supported auth method (header, bearer, basic, query) and upload format (raw, multipart, json), with a startup key check, capture hook, timestamping, and retry with backoff.
auvi-ingest.mjs
AUVI_ENDPOINT="https://your-site.lovable.app/api/public/ingest" \
AUVI_KEY="$AUVI_KEY" \
AUVI_AUTH=header AUVI_FORMAT=raw AUVI_INTERVAL=10 \
AUVI_CAPTURE="ffmpeg -y -rtsp_transport tcp -i rtsp://user:pass@10.0.0.42:554/stream1 -frames:v 1 -q:v 3 frame.jpg" \
node auvi-ingest.mjs ./frame.jpgauvi_ingest.py
AUVI_ENDPOINT="https://your-site.lovable.app/api/public/ingest" \
AUVI_KEY="$AUVI_KEY" \
AUVI_AUTH=bearer AUVI_FORMAT=multipart AUVI_INTERVAL=10 \
AUVI_CAPTURE="fswebcam -r 1920x1080 --no-banner frame.jpg" \
python3 auvi_ingest.py ./frame.jpgauvi-rtsp-ingest.sh
chmod +x auvi-rtsp-ingest.sh
AUVI_ENDPOINT="https://your-site.lovable.app/api/public/ingest" \
AUVI_KEY="$AUVI_KEY" \
RTSP_URL="rtsp://user:pass@10.0.0.42:554/stream1" \
AUVI_INTERVAL=10 AUVI_AUTH=header \
./auvi-rtsp-ingest.shauvi_onvif_ingest.py
# discover profiles and snapshot URLs first
ONVIF_HOST=10.0.0.42 ONVIF_USER=admin ONVIF_PASS=secret \
python3 auvi_onvif_ingest.py --list
AUVI_ENDPOINT="https://your-site.lovable.app/api/public/ingest" \
AUVI_KEY="$AUVI_KEY" \
AUVI_AUTH=header AUVI_FORMAT=raw AUVI_INTERVAL=10 \
ONVIF_HOST=10.0.0.42 ONVIF_USER=admin ONVIF_PASS=secret \
python3 auvi_onvif_ingest.pyResponses
Rate limits
Limits are derived from each camera's own capture interval, not a fixed global number — a one-second camera and an hourly camera each get their own allowance with room for retries and catch-up bursts.
Good to know
- Device keys are stored only as a SHA-256 hash — keep your copy safe, and ask us to rotate it if a device is lost or replaced.
- A camera goes online the moment its first frame lands. Until then the public view shows a "waiting for first frame" standby screen — it never shows sample imagery in place of your site.
- Frames are stored privately; public views render short-lived signed URLs, watermarked and download-free.
- Posting faster than the camera's cadence is fine — the live view always shows the newest frame and the rest go into the archive.