Build with PerceptDB
The live multimodal data cloud. Store objects, embeddings, events, and metadata — query with SQL, API, CLI, or natural language.
Quickstart
A complete pipeline — here, from an RTSP camera — to natural-language search. The same flow works for logs, sensors, audio, and any other stream.
Sign up at perceptdb.com, pick a plan, and grab an API key from Settings → API keys.
export PERCEPT_KEY="pk_live_…" # Settings → API keys
In the console: Streams → New stream → paste an RTSP/HLS URL (cameras) — capture and perception start automatically. For logs, sensors, or events, push JSON with the stream's ingest token:
curl -X POST https://perceptdb.com/api/v1/streams/{streamId}/events \
-H "Authorization: Bearer pk_strm_…" \
-H "Content-Type: application/json" \
-d '{"body": "FATAL: connection pool exhausted", "payload": {"service": "api"}}'curl -X POST https://perceptdb.com/api/v1/objects \
-H "Authorization: Bearer $PERCEPT_KEY" \
-d '{"filename": "dock-cam.mp4", "mimeType": "video/mp4", "sizeBytes": 1234567}'
# → PUT your bytes to the returned uploadUrl, then:
curl -X POST https://perceptdb.com/api/v1/objects/{objectId}/complete \
-H "Authorization: Bearer $PERCEPT_KEY"curl -X POST https://perceptdb.com/api/v1/search \
-H "Authorization: Bearer $PERCEPT_KEY" \
-d '{"query": "person carrying a package near the entrance"}'Console → Alerts → New alert. Pick a ready-made preset (after-hours person, error in logs, sensor over limit…) or describe a custom watch in plain English — matches notify you by bell + Discord.
Core concepts
Five primitives. Everything is built from these.
SDKs
Official Python and TypeScript SDKs are in development. The REST API is a plain JSON interface, so any HTTP client works today:
Python (requests)
import requests
r = requests.post(
"https://perceptdb.com/api/v1/search",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"query": "person carrying a package near the entrance"},
)
hits = r.json()["hits"]TypeScript (fetch)
const r = await fetch("https://perceptdb.com/api/v1/search", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PERCEPT_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ query: "person in a red hoodie at the loading dock" }),
});
const { hits } = await r.json();Percept SQL
Postgres-compatible plus multimodal functions.
Semantic + metadata hybrid query
SELECT object, stream, caption, capture_ts,
SEMANTIC_SCORE('person carrying cardboard box') AS score
FROM frame_captions
WHERE stream ILIKE '%dock%'
AND capture_ts > NOW() - INTERVAL '24 hours'
ORDER BY score DESC NULLS LAST
LIMIT 20;Time-window event query
SELECT s.name AS stream, e.event_type, e.count, e.created_at
FROM object_events e
JOIN streams s ON s.id = e.stream_id
WHERE e.event_type = 'person_present'
AND (e.created_at AT TIME ZONE 'America/Los_Angeles')
> (NOW() AT TIME ZONE 'America/Los_Angeles')::date
ORDER BY e.created_at DESC;What you can query
REST API
Base URL: https://perceptdb.com/api/v1 · auth via Authorization: Bearer <api key>
/v1/searchSemantic search across text + video frames/v1/sqlRead-only SQL over your project views (SEMANTIC_SCORE supported)/v1/objectsList objects/v1/objectsStart an upload (returns a presigned PUT URL)/v1/objects/{id}/completeFinish an upload — perception runs automatically/v1/objects/{id}Object metadata + download URL/v1/eventsQuery derived perception events/v1/indexesVector index stats/v1/streams/{id}/eventsPush events to a stream (per-stream ingest token)/v1/streams/{id}/webhookWebhook ingest (Slack, GitHub, custom)Vectors (bring your own embeddings)
Use PerceptDB as an encrypted vector + metadata store: create an index with a fixed dimension, then read and write raw vectors with documents and metadata attached.
/v1/indexesCreate a vector index ({ name, dim, metric? })/v1/indexes/{name}Delete an index and its vectors/v1/vectors/{index}Upsert vectors ({ items: [{ id, vector, document?, metadata? }] })/v1/vectors/{index}/queryNearest-neighbor search ({ vector | vectors, topK?, where? })/v1/vectors/{index}/getFetch by ids or metadata filter ({ ids? | where?, offset?, limit? })/v1/vectors/{index}/updateReplace metadata for existing ids ({ ids, metadatas })/v1/vectors/{index}/deleteDelete by ids or metadata filter ({ ids? | where? })/v1/vectors/{index}/countVector count for an indexCreate an index once (its dimension is fixed), then upsert your own embeddings with documents and metadata, and query with metadata filters. Index names are lowercase [a-z0-9_-]; each is its own encrypted store.
# 1. create an index (768-dim, cosine)
curl -X POST https://perceptdb.com/api/v1/indexes \
-H "Authorization: Bearer $PERCEPT_API_KEY" \
-H "content-type: application/json" \
-d '{"name":"documents","dim":768,"metric":"cosine"}'
# 2. upsert vectors you embedded yourself (up to 512 per request)
curl -X POST https://perceptdb.com/api/v1/vectors/documents \
-H "Authorization: Bearer $PERCEPT_API_KEY" \
-H "content-type: application/json" \
-d '{"items":[
{"id":"doc-1","vector":[0.01, -0.02, ...],
"document":"quarterly report intro",
"metadata":{"project":"acme","admin_id":"u1"}}
]}'
# 3. nearest-neighbor query with a metadata filter
curl -X POST https://perceptdb.com/api/v1/vectors/documents/query \
-H "Authorization: Bearer $PERCEPT_API_KEY" \
-H "content-type: application/json" \
-d '{"vector":[0.01, -0.02, ...], "topK":10,
"where":{"project":{"$eq":"acme"}}}'Filters accept $eq $ne $in $nin $lt $lte $gt $gte and $and $or. Vectors are stored encrypted; the same rows are visible in SQL via api.embeddings.
CLI
A percept CLI is in development. Until it ships, everything is available through the console and the REST API above — the API examples in the Quickstart cover the same flows (upload, ingest, search, SQL).