Search
Phase 2 search-api and the search-client SDK
Phase 2 is a read-only Express service (search-api). It embeds the query with the same gateway/model family used at index time, then runs similarity search against the chosen path's vectors (or weighted RRF for hybrid).
The admin UI can also search through indexer-api. Prefer search-api + search-client for product integrations so search scales independently of indexing.
Endpoints
| Method | Route | Index |
|---|---|---|
POST | /search | Multimodal chunks |
POST | /transcribe/search | Transcript segments |
POST | /frames/search | Still frames |
POST | /hybrid/search | Hybrid weighted RRF |
GET | /health | Liveness |
GET | /ready | Gateway key + schema present (503 if not) |
Common body fields
{
"query": "sunset over water",
"collectionId": "col_abc",
"collectionIds": ["col_a", "col_b"],
"uploadId": "upload_xyz",
"limit": 10
}All filters are optional. limit defaults to 10 (max 50). Empty database → empty results array, not an error.
Hybrid extras
{
"query": "product demo with voiceover",
"weights": { "video": 1, "speech": 1.5, "vision": 0.8 },
"rrfK": 60,
"perModalityLimit": 50
}See Hybrid for process → RRF → costs and retry semantics.
Install the SDK
In this monorepo:
pnpm build:packages{
"dependencies": {
"search-client": "workspace:*"
}
}Usage
import { SearchClient, SearchApiError } from "search-client";
const client = new SearchClient({
baseUrl: "http://search-api:3001", // internal hostname in deploy
timeoutMs: 30_000,
});
const { ready } = await client.ready();
if (!ready) throw new Error("search-api not ready");
const multimodal = await client.search({
query: "sunset over water",
collectionId: "col_abc",
limit: 10,
});
const speech = await client.searchTranscripts({
query: "quarterly revenue guidance",
});
const frames = await client.searchFrames({
query: "red product box on a table",
});
const hybrid = await client.searchHybrid({
query: "product demo with voiceover",
weights: { video: 1, speech: 1.5, vision: 0.8 },
});
try {
await client.search({ query: "" });
} catch (err) {
if (err instanceof SearchApiError) {
console.error(err.status, err.message);
}
}Hit shapes (summary)
| Method | Notable fields |
|---|---|
search | segmentId, chunkIndex, startSec, endSec, score, sourceObject |
searchTranscripts | same timing fields + text |
searchFrames | frameId, timestampSec, thumbnailObject, sourceObject |
searchHybrid | timing + rrfScore, ranks, sources, optional text / thumbnailObject |
Resolve media with sourceObject.bucket + sourceObject.key against your S3-compatible store. The SDK returns object refs, not playback URLs. Presigned URL generation stays in the admin UI (and in your product layer) on purpose: the search stack does not opinionate how objects are served, so you can plug in your own authz, DRM, CDN, or private bucket policies.