Documentation

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.

1Create your workspace

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
2Connect a stream

In the console: Ingest → Streams → Connect source. Camera capture starts from an RTSP/HLS URL. For logs, choose CloudWatch, Datadog, OpenTelemetry, Fluent Bit, or HTTP and copy the generated configuration:

curl -X POST https://perceptdb.com/api/v1/logs/{streamId} \
  -H "Authorization: Bearer pk_strm_…" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary '{"timestamp":"<CURRENT_ISO_TIMESTAMP>","service":"api","level":"error","message":"connection pool exhausted"}'
3Or upload files directly
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"
4Search in plain English
curl -X POST https://perceptdb.com/api/v1/search \
  -H "Authorization: Bearer $PERCEPT_KEY" \
  -d '{"query": "person carrying a package near the entrance"}'
5Add an alert

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.

Object
A file or media asset: image, video, PDF, audio, document, blob
Record
A structured row: camera, delivery, customer, shipment, claim
Embedding
A vector representation: image, text, video segment, audio, multimodal
Event
A timestamped meaningful occurrence: package delivered, door opened, signature detected
Trace
The provenance chain: raw object → segment → embedding → event → query result → evidence

SDKs

The repository includes dependency-light Python and TypeScript SDKs over the same project-scoped REST API. Any ordinary HTTP client also works.

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()["data"]["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 { data } = await r.json();
const hits = data.hits;

Time series

Store logs, metrics, sensor readings, market ticks, and timestamped events in the ClickHouse-backed temporal plane. Writes are idempotent and confirmed, queries are bounded and project-scoped, and aggregates expose safe functions and windows. A confirmed response covers raw storage; live publication, optional embedding, and perception finish asynchronously through durable retry.

Confirmed writes default to 600 requests per minute per API key. Raw queries, aggregates, and series catalog requests use 120-request-per-minute buckets per API key; raw queries and series share one bucket. Shared API-key and project ceilings still apply, so honor 429 responses and the server-provided retryAfter delay.

Connect log streams

Open Ingest → Streams → Connect source → Logs. Percept creates a project-scoped log stream and shows its token once. The wizard generates exact setup values for CloudWatch through Data Firehose, Datadog Observability Pipelines, OpenTelemetry Collector, Fluent Bit, or direct JSON and NDJSON. Common service, environment, host, severity, trace, and span fields become filterable dimensions while the complete bounded source record remains available.

# Direct JSON or NDJSON
curl -X POST https://perceptdb.com/api/v1/logs/{streamId} \
  -H "Authorization: Bearer $PERCEPT_STREAM_TOKEN" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary '{"timestamp":"<CURRENT_ISO_TIMESTAMP>","service":"api","level":"error","message":"database unavailable"}'

# OpenTelemetry Collector appends /v1/logs to this base endpoint:
https://perceptdb.com/api/v1/logs/{streamId}/otlp

# Amazon Data Firehose uses the stream token as X-Amz-Firehose-Access-Key:
https://perceptdb.com/api/v1/logs/{streamId}/firehose

The repository CloudFormation template at examples/log-connectors/aws-cloudwatch/template.yamlcreates a Firehose destination, encrypted failed-delivery S3 backup, scoped IAM roles, and one exact CloudWatch subscription filter. Keep Firehose request compression off and use a 1 MiB HTTP buffer.

Write a metric batch

Replace <CURRENT_ISO_TIMESTAMP> with a current, timezone-qualified ISO 8601 value inside the stream retention period.

curl -X POST https://perceptdb.com/api/v1/timeseries \
  -H "Authorization: Bearer $PERCEPT_KEY" \
  -H "Idempotency-Key: sensor-a-000042" \
  -H "Content-Type: application/json" \
  -d '{"streamId":"11111111-1111-4111-8111-111111111111","events":[{
    "eventId":"sensor-a:42",
    "ts":"<CURRENT_ISO_TIMESTAMP>",
    "kind":"sensor",
    "metric":"temperature.celsius",
    "value":21.7,
    "unit":"C",
    "attributes":{"site":"warehouse-1"}
  }]}'

Query typed temporal evidence

Raw and aggregate queries accept exact top-level filters joined with AND. Use anyOf for 1 to 8 validated OR clauses. Fixed body classes cover errors, timeouts, and explicit HTTP statuses without accepting regular expressions or raw ClickHouse SQL.

curl -X POST https://perceptdb.com/api/v1/timeseries/query \
  -H "Authorization: Bearer $PERCEPT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "start":"<START_ISO>",
    "end":"<END_ISO>",
    "kinds":["percept_self_log"],
    "anyOf":[
      {"attributes":{"status_code":["500","502","503"]}},
      {"bodyClass":{"kind":"http_status","statuses":[500,502,503]}}
    ],
    "order":"desc",
    "limit":100
  }'

Query raw records with POST /timeseries/query, aggregate windows with POST /timeseries/aggregate, or resume live SSE with GET /timeseries/{streamId}/tail. Tail replay is contiguous only inside the retained Postgres replay window. A gap event identifies any missing inclusive sequence range; recover still-retained records with a bounded raw query. Text filters use case-insensitive token-any matching. Public writes reject s3Key; put a project-owned object ID in payload when a record refers to bytes. The Time series console uses the same engine. Search Studio can answer deterministic log and metric questions such as the latest error, HTTP failures by route, and a named metric's average or p95 with explicit ClickHouse coverage.

Percept SQL

Read-only SQL-style queries across every Percept store.

One query across stores

Query Workbench uses PostgreSQL as the final engine. Bounded virtual relations add semantic and vector evidence, time-series records, and graph relationships to the same statement, while the authenticated project scope is injected by the server. Generate SQL understands these relations, including prompts such as what was the last error?

WITH evidence AS (
  SELECT * FROM SEARCH('package delivery', 20)
  UNION ALL
  SELECT * FROM TIMESERIES_QUERY(
    '{"start":"<START_ISO>","end":"<END_ISO>","text":"package delivery","order":"desc","limit":20}'
  )
  UNION ALL
  SELECT * FROM GRAPH_TRAVERSE(
    '{"entities":["package"],"maxDepth":2,"limit":20}'
  )
)
SELECT store, kind, captured_at, score, text, subject, predicate, object
FROM evidence
ORDER BY captured_at DESC NULLS LAST, score DESC NULLS LAST
LIMIT 60;

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

SEMANTIC_SCORE
Encrypted vector similarity as a float column
frame_captions
Per-chunk scene readings + transcripts
object_events
Derived events (person_present, loitering…)
detections
Bounding boxes, when a detector runs
stream_events
Raw ingested records (body + payload JSON)
objects / streams
Your media + sources

REST API

Base URL: https://perceptdb.com/api/v1 · auth via Authorization: Bearer <api key>. Routes below are relative to this base URL.

POST/searchSemantic search across text + video frames
POST/sqlRead-only SQL over your project views (SEMANTIC_SCORE supported)
GET/objectsList objects
POST/objectsStart an upload (returns a presigned PUT URL)
POST/objects/{id}/completeFinish an upload; perception runs automatically
GET/objects/{id}Object metadata + download URL
GET/eventsQuery derived perception events
GET/indexesVector index stats
GET/streamsList registered streams
POST/streamsCreate a stream and one-time device token
POST/streams/{id}/eventsPush events to a stream (per-stream ingest token)
POST/streams/{id}/webhookWebhook ingest (Slack, GitHub, custom)
POST/logs/{id}JSON, NDJSON, Datadog, and Fluent Bit log ingest
POST/logs/{id}/otlp/v1/logsOTLP/HTTP JSON log ingest
POST/logs/{id}/firehoseAmazon Data Firehose HTTP destination
POST/timeseriesConfirmed idempotent log, metric, sensor, and event write
POST/timeseries/queryBounded raw temporal query with cursor
POST/timeseries/aggregateSafe windowed aggregates
GET/timeseries/seriesSeries and metric catalog
GET/timeseries/healthEngine and delivery outbox health
GET/timeseries/{streamId}/tailReplayable SSE tail

Vectors (managed or bring your own embeddings)

Use PerceptDB as an encrypted vector + metadata store. Create an index with a fixed dimension and optionally a managed embedding model, then write raw content or vectors with documents and metadata attached. Managed writes automatically bind an immutable processing run to a canonical pipeline, embedding profile, and index generation; use pipeline search to keep retrieval in that exact vector space.

POST/indexesCreate a vector index ({ name, dim, metric?, embeddingModel? })
DELETE/indexes/{name}Delete an index and its vectors
POST/vectors/{index}Upsert vectors or managed inputs ({ items: [{ id, vector? | embeddingInput?, ... }] })
GET/vector-operations/{id}Check an asynchronous vector mutation receipt
POST/vectors/{index}/queryNearest-neighbor search ({ vector | vectors | query | queries, topK?, where? })
GET/vectors/{index}/{id}/lineageInspect the exact processing profile, artifact, model, and index generation
POST/pipelines/{pipeline}/searchCanonical managed-text search through the bound model/index generation
POST/vectors/{index}/getFetch by ids or metadata filter ({ ids? | where?, offset?, limit? })
POST/vectors/{index}/updateReplace metadata for existing ids ({ ids, metadatas })
POST/vectors/{index}/deleteDelete by ids/filter, or durably reset with an empty body
GET/vectors/{index}/countVector count for an index

Create an index once (its dimension and model contract are fixed), then upsert raw content or your own embeddings 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"}}
      ]}'

# For durable asynchronous upsert, metadata update, delete, or full reset, add
# both headers. A 202 response means
# the complete request is encrypted and queued; repeat the same key and body
# to recover the same receipt after a timeout.
curl -X POST https://perceptdb.com/api/v1/vectors/documents \
  -H "Authorization: Bearer $PERCEPT_API_KEY" \
  -H "content-type: application/json" \
  -H "Prefer: respond-async" \
  -H "Idempotency-Key: leeloo-batch-123" \
  -d @vectors.json

# Empty-body delete is intentionally reset-only and requires both async headers.
curl -X POST https://perceptdb.com/api/v1/vectors/documents/delete   -H "Authorization: Bearer $PERCEPT_API_KEY"   -H "content-type: application/json"   -H "Prefer: respond-async"   -H "Idempotency-Key: leeloo-reset-123"   -d '{}'

curl https://perceptdb.com/api/v1/vector-operations/$OPERATION_ID \
  -H "Authorization: Bearer $PERCEPT_API_KEY"

# 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. Processing executions and parser/chunker artifacts are available through api.processing_runs and api.processing_artifacts. Terminal asynchronous receipts remain recoverable by idempotency key for 30 days; active operations never expire. A filtered asynchronous delete resolves one bounded exact-id snapshot before durable acceptance and refuses a saturated scan.

CLI

The dependency-free percept CLI is in the repository. It supports authentication, objects, vectors, search, SQL, streams, and time-series write, query, aggregate, series, health, and tail commands.

percept auth login --key "$PERCEPT_KEY"
percept timeseries health
percept timeseries query --start '<START_ISO>' --end '<END_ISO>'

Replace the start and end placeholders with current, timezone-qualified ISO 8601 values.

Security & governance

Encryption at rest
AES-256 · vectors encrypted (CyborgDB)
Encryption in transit
TLS 1.3 everywhere
RBAC
Org → Project → Resource scopes
Audit logs
Org-wide action trail
Tenant isolation
Enforced at the database layer
Compliance
Security review available · certifications on the roadmap