Niravi Docs
npm ↗ Get a key ↗

Search

Find moments, pages and boxes across every video, photo and document in your workspace, by meaning, not just keywords.

POST /api/v1/search runs a multimodal embedding search over frames, transcripts, and scene descriptions, fused into one ranked list.

curl https://api.niravi.io/api/v1/search \
  -H "X-API-Key: $NIRAVI_API_KEY" -H "Content-Type: application/json" \
  -d '{"query": "two people arguing in a kitchen", "limit": 10, "moments_per_video": 3}'
FieldTypeNotes
querystringrequired — natural-language description
limitintresults to return (server default applies if omitted); counts moments
moments_per_videoint1 to 10, default 1: up to this many moments per video, each a separate hit with its own timestamp. Moments of one video that sit close together merge into one. Ignored with video_id (a search inside one video already returns every matching moment)
video_idstringrestrict to a single video
search_optionsobjectadvanced tuning (fusion weights, modalities)
rerankboolapply a second-pass re-ranker
enginestringarrow (current) or classic (NiQu, deprecated). Omit for your workspace’s default: Arrow for every new workspace, and for any workspace that is empty or holds only Arrow assets. A library indexed by NiNo still defaults to classic, so pass arrow there to search what you upload
workspace_idsstring[]search several workspaces at once (federated)

Each hit carries video_id, timestamp, frame_url (the JPEG of that moment) and two scores:

FieldMeaning
score0..1, the best hit of the search is 1.0. A whole-library Arrow search normalises against the whole candidate pool, so scores compare across the videos of one result list (not across searches)
raw_scorethe hit’s fused score before pool normalisation. The weakest candidate of the pool reads 0, so rank and compare with score

With engine: arrow a hit also says what it landed on:

FieldMeaning
asset_kindvideo, image or document
pagedocuments: the 1-based page
passagedocuments: the passage of the page that best matches the query
regionphotos and pages: the best-matching box, {x, y, w, h, label, source} normalized top-left

The response’s engine and recipe say which engine answered; engine_fallback explains when another one had to. See Images & documents.

Only finished assets. An asset that is still processing (or failed) never appears in results, even when part of it is already indexed.

Speed. The first search of a library takes about 4 seconds while its index loads; after that, searches answer in under a second. A finished upload is searchable within seconds of completing (3 to 7 s measured).

import requests  # the Python SDK is not on PyPI yet: plain HTTP works

BASE = "https://api.niravi.io/api/v1"
H = {"X-API-Key": "nv_..."}

r = requests.post(f"{BASE}/search", headers=H,
                  json={"query": "two people arguing in a kitchen", "limit": 10, "moments_per_video": 3})
for hit in r.json()["data"]["results"]:
    print(hit["video_id"], hit["timestamp"], hit["score"], hit["raw_score"])
const hits = await niravi.search("two people arguing in a kitchen", { limit: 10, momentsPerVideo: 3 });
const full = await niravi.searchResponse("two people arguing in a kitchen", { engine: "arrow" }); // + engine, engine_fallback

Typed search narrows to a single dimension: POST /api/v1/search/{type} where type is character, location, music, or emotion. The body takes query, limit and video_id; a character search can instead take an example face, cluster_id with its source_video_id.

curl https://api.niravi.io/api/v1/search/emotion \
  -H "X-API-Key: $NIRAVI_API_KEY" -H "Content-Type: application/json" \
  -d '{"query": "tense and suspenseful"}'

On an Arrow workspace each type searches these signals:

TypeSearchesNot available
locationshot captions + scene labels
emotionshot captions + sound events + mood musicfacial expression, scene mood
musicsound events + audio segments (genre, instrument, music segments)song title and artist identification
charactera query that names a workspace Person (part of the name, or the exact person id) returns that person’s faces; cluster_id + source_video_id returns the faces of that cluster; shot captions answer a description

meta.search says which legs ran, how many hits each gave, and which facets are unavailable and why:

"meta": { "search": {
  "type": "emotion", "engine": "arrow",
  "legs": { "dense": { "hits": 1 }, "sound_events": { "hits": 0 }, "mood_music": { "hits": 0 } },
  "unavailable": { "facial_expression": "facial expression is not indexed by Arrow", "scene_mood": "scene mood is not written by Arrow" }
} }
requests.post(f"{BASE}/search/character", headers=H, json={"query": "a man in a red jacket"})
requests.post(f"{BASE}/search/character", headers=H, json={"cluster_id": 1, "source_video_id": video_id})
requests.post(f"{BASE}/search/location", headers=H, json={"query": "a rainy city street at night"})
await niravi.magicSearch("music", "upbeat synth");
await niravi.magicSearch("emotion", "joyful");

Search a single video instead of the whole corpus by passing video_id (semantic) or scoping a guide’s per-video endpoints — see Videos & processing.


The science behind this → Niravi’s retrieval engine (frame + transcript + scene fusion) is documented in the Engineering Notes at blog.niravi.io/blog — the pipeline generation by generation, with the head-to-head retrieval benchmarks vs the commercial leader.