Search

Natural-language catalog search — request limits, query understanding, scoring, and coverage.

POST /v1/search searches your airline’s catalog (plus the shared global catalog) in natural language. You send a passenger-style query; the platform parses it into structured constraints, ranks the matching titles semantically, and tells you exactly how it understood the query.

Request

1{
2 "query": "romantic comedy from the 90s",
3 "topK": 5,
4 "kindOverride": null
5}
FieldRequiredRules
queryyes1–1000 characters. Longer is rejected with 422 request.validation.
topKno1–100; defaults to 10. Out of range is a 422.
kindOverrideno"Movie" or "TV", any casing (movie, tv, TV all work). Forces the content kind instead of the parsed one. Anything else is a 422.

The request body is capped at 64 KiB (413 above it). There is no airline field — your credential carries the airline.

How your query is understood

Every response echoes the parse in parsed, so you never have to guess what the platform did with the text:

  • originalQuery — your text, verbatim.
  • filters — the structured constraints extracted from it: yearFrom / yearTo, genres[] (all listed genres must match), cast[], titleContains, contentKind, ratingMax — plus airlineId, the tenant your credential resolved to (server-side; you never send it).
  • semanticText — the meaning left over after constraint extraction (“feel-good”), used for semantic ranking. Empty when nothing remains.
  • source — which parser produced the filters: "rules" (the deterministic engine) or "ai".

Parsing handles the phrasings passengers actually use:

  • Decades: ”90s” or “1990s” ⇒ yearFrom: 1990, yearTo: 1999.
  • Compound genres: “romcom” ⇒ genres: ["Romance", "Comedy"].
  • Kind keywords: “shows”, “series”, “TV” ⇒ contentKind: "TV".

Current limitation. In the current release, parsing runs on the deterministic rules engine ("source": "rules"), which does not extract cast names from query text. Fuzzy correction of well-known names (“Tom Cruz” → “Tom Cruise”) arrives with the AI parser in a later release. A query like “movies with Tom Cruise” still searches semantically — it just won’t produce a structured cast filter yet.

Results & scoring

Ranking is a fixed pipeline: structured filters prune the candidate set, vector similarity ranks the survivors, and an exact-title containment match adds a fixed boost on top of the semantic score.

Each result carries:

FieldMeaning
itemIdCatalog item id ("M:…" movie / "T:…" TV)
title, year, kind, genres, castThe catalog metadata that matched
scoreFinal ranking score (semantic + any exact-title boost)
cosineScoreThe raw semantic-similarity component
reasonsHuman-readable match evidence, e.g. "Year 1996", "semantic 0.81"

candidateCount is how many items survived the filters; totalItems is the size of your visible catalog.

Coverage, reported honestly

coverage tells you how complete the metadata behind your results is — reported, never papered over:

1{
2 "movieCount": 38,
3 "tvSeriesCount": 7,
4 "genreCoveragePct": 100,
5 "castCoveragePct": 100,
6 "synopsisCoveragePct": 100
7}

The three …Pct fields are percentages on the 0–100 scale (42.5 means 42.5%), never 0–1 fractions. They report how many of your visible items carry at least one genre, at least one cast member, and a synopsis respectively — if a coverage number is low, semantic ranking quality is limited by the catalog metadata, not by your queries. The sandbox catalog currently reports 100 on all three.

Worked example

$curl -X POST "https://api.staging.westiq.ai/v1/search" \
> -H "Authorization: Bearer $ACCESS_TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"query": "romantic comedy from the 90s", "topK": 5}'

Abridged response (sandbox tenant):

1{
2 "schemaVersion": "1",
3 "parsed": {
4 "originalQuery": "romantic comedy from the 90s",
5 "filters": {
6 "airlineId": "demo_airline",
7 "yearFrom": 1990,
8 "yearTo": 1999,
9 "genres": ["Romance", "Comedy"]
10 },
11 "semanticText": "",
12 "source": "rules"
13 },
14 "results": [
15 {
16 "itemId": "M:…",
17 "title": "You've Got Mail",
18 "year": 1998,
19 "kind": "Movie",
20 "score": 0.71,
21 "reasons": ["Year 1998", "Genres: Romance, Comedy", "semantic 0.71"]
22 },
23 {
24 "itemId": "M:…",
25 "title": "Sleepless in Seattle",
26 "year": 1993,
27 "kind": "Movie",
28 "score": 0.69,
29 "reasons": ["Year 1993", "Genres: Romance, Comedy", "semantic 0.69"]
30 }
31 ],
32 "candidateCount": 6,
33 "totalItems": 45,
34 "coverage": {
35 "movieCount": 38,
36 "tvSeriesCount": 7,
37 "genreCoveragePct": 100,
38 "castCoveragePct": 100,
39 "synopsisCoveragePct": 100
40 },
41 "correlationId": "req_0197c0a1b2c3d4e5f60718293a4b5c6d"
42}

Full request and response schemas are in the API Reference. Error responses use the standard error envelope.