FalconVQA Docs
API Reference

Search

POST /query — vector search over chunk records, with typed filters, named vectors and three detail levels.

POST /query finds segments. For a question whose answer is a conclusion rather than a location, use POST /ask.

application/json.

Body

FieldTypeDefaultNotes
textstringrequiredThe search query
video_idsstring[] | nullnullRestrict to these videos. null searches every video in the install
analyzerstringdefault_videoWhich analyzer's output to search
fieldstringcombinedWhich named vector to compare against
limitint51–50
score_thresholdfloat | nullnullDrop weaker matches
synthesizebooltrueWhether to generate an answer over the hits
detailstringstandardminimal | standard | full
filtersobject{}Any filter from GET /schema

Example

curl -X POST http://127.0.0.1:8077/query \
  -H "X-Core-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "man in yellow shorts walking toward the registers",
    "video_ids": ["95e110e25070fcfc"],
    "analyzer": "default_video",
    "field": "people",
    "limit": 5,
    "score_threshold": 0.5,
    "synthesize": true
  }'
{
  "query": "man in yellow shorts walking toward the registers",
  "analyzer": "default_video",
  "field": "people",
  "detail": "standard",
  "answer": "A man in a white T-shirt and yellow shorts walks down the centre aisle toward the registers [95e110e25070fcfc 77.80-83.80s].",
  "results": [ { "…": "SearchResult" } ]
}

answer is null when synthesize is false or nothing matched. It is never fabricated from an empty result set.

field — which vector

Each chunk is one point carrying several named vectors.

FieldEmbeds
combinedThe whole flattened record (default)
descriptionProse summary only
peoplePerson descriptions only
actionsActions and what objects are used for
objectsObject names only

combined covers everything; the others match one part, so a short precise match is not diluted by surrounding prose. A chunk missing that field is excluded, not matched on empty text — a video with no people cannot surface in a field=people search.

detail — how much comes back

A browser and an agent want opposite things from the same hit. Five full hits measured about 19k tokens against ~440 for the same hits at minimal.

LevelContents
minimalvideo_id, chunk_id, start, end, timecode, score, 180-char snippet
standardplus video_url, description, people, objects, actions, tags, speakers, people_count, turns
fullplus text, persons, detections, texts

The agent pattern: detail=minimal to choose, then GET /videos/{id}/chunks?chunk_ids=… to read the few that matter.

filters

A flat dict, validated against one spec, so a filter added to the store is reachable here immediately — no plumbing.

{
  "filters": {
    "chunk_ids": [2, 4, 7],
    "video_ids": ["95e110e25070fcfc"],
    "analyzer_ids": ["people"],
    "after": 60,
    "before": 300,
    "min_people": 2,
    "max_people": 9,
    "objects": ["shopping cart"],
    "tags": ["checkout"],
    "speakers": ["SPEAKER_00"],
    "people": ["cashier"],
    "chunk_config": "interval:30"
  }
}
FilterTypeMatch
video_idslist[str]any
chunk_idslist[int]any
analyzer_idslist[str]any
chunk_configstrexact
objects, tags, speakers, peoplelist[str]any
min_people / max_peopleint≥ / ≤
after / beforefloatstart / ≤ end

Unknown keys are a 400 with a suggestion — Unknown filter 'speaker'; did you mean 'speakers'? — rather than being ignored. A silently dropped filter returns plausible but wrong results.

Every filter is a hard AND against exact stored labels, so it can only ever remove results. Speculative filters ("white shirt" as an object) quietly reduce a good result set to nothing.

video_ids and analyzer stay first-class parameters because every caller uses them; they simply join the filter dict on the way down.

score_threshold

Cosine similarity always ranks something, so without a threshold a query for absent content returns weak neighbours instead of nothing.

Measured on sample data: present content scored ≥ 0.665, absent ≤ 0.524 — so roughly 0.55–0.60 separates them. Retune per field and per embedding model; appearance queries (field=people) score lower than topic queries, around 0.51–0.54.

Set it only when the question is whether something is absent and an empty result has to mean "not present".

SearchResult

FieldTypeNotes
video_idstringContent hash of the source file
video_urlstringPublic Storage URL of the source video
chunk_idintIndex within its video — only meaningful with video_id
start, endfloatSeconds; use these to seek
timecodestringHuman form, e.g. "1:17.80-1:23.80"
scorefloatCosine similarity, 0–1
snippetstringminimal only — first 180 characters
descriptionstringProse description
people, objects, actions, tagsstring[]Structured facets; empty for analyzers that do not produce them
speakersstring[]Speakers heard in the chunk (diarization)
people_countint | nullFrom the people analyzer
turnsobject[]{speaker, start, end, text} — who said what, with its own seek time
textstringfull only — the text that was embedded
persons, detections, textsobject[]full only — every nested record

The payload is self-sufficient: everything needed to cite and play a moment is on the hit, with no lookup into records.

Errors

CodeCause
400Unknown analyzer, field, detail, or filter key
401Missing or wrong token

On this page