Catalog Serve
The age-gated catalog read — unscored (GET) and per-member scored (POST).
/v1/catalog/serve returns your airline’s visible catalog, age-gated. It
has two verbs:
GET— the unscored read: the full visible catalog in a stable order, age-gated to a seat/member if you name one.POST— the scored serve: the same age-gated catalog ranked for one member by their bound persona and affinity.
Both apply the age-tier model and the fail-closed locator rules: an unknown or unbound member is served the most-restrictive (child-safe) catalog, never the full one, and never an error.
Content kinds
The kind filter accepts all four kinds the catalog carries — Movie, TV,
Audio, Game — on both verbs, in any casing. Omitting it applies no
kind filter at all.
Serve takes canonical values only — no synonyms. music, albums, and
games resolve on search’s kindOverride but are a
422 request.validation here. The difference is deliberate: a free-text
query surface and a structured filter parameter are not the same thing.
Don’t assume parity because you learned the search vocabulary first.
A child/tween serve for Audio or Game returns an empty list today.
The rating vocabulary carries no audio/game scale yet, so those titles are
ingested with a null rating
— and a null rating fails closed for every tier but Adult. So a serve
for a Child, Tween, or Unknown member with kind=Audio or
kind=Game returns an empty items list with the exclusion counted
honestly in ageExcludedCount — never an error. Those titles stay hidden
from younger members until a real rating vocabulary lands; they serve
normally to Adult members.
GET — unscored read
Airline-scoped — no
X-Session-Id. All query parameters are optional:
titleIdis an integer (see the note on ids).year: 0means an unknown year; every other unknown field is omitted (the API never sends a field asnull, and never a0placeholder).totalItemsis the size of what this request could serve before age exclusion;ageExcludedCountis how many the gate removed — reported honestly, never papered over.totalItems − ageExcludedCountis the servable total.ageTierAppliedis the tier the server resolved for your locator (Unknownis shown as-is and behaves asChild).
Item fields
Both verbs serve the same item shape (the scored POST adds score and
matchReason on top). Render runtime, language, and review badges straight
off the response — there is no metadata sidecar to fetch:
Your own ids on serve responses
Name a sourceSystem on the request (GET query parameter, POST body field)
and each served item echoes the externalTitleId you ingested it under —
so you can key your own state on serve responses without holding a reverse
map of the catalog:
externalTitleId is present only for titles with a confirmed ingest
mapping under that namespace. It is omitted for everything else — titles
you never ingested (the shared global catalog, another feed’s titles), and
titles whose ingest batch has not completed
yet. An item without the echo is still
fully servable; the omission means “not your id”, not “not available”.
POST — scored serve
The keystone personalization read: the age-gated catalog ranked for one
member. This is session-scoped —
it requires the X-Session-Id header. Body (max 4 KiB):
Everything from the GET item, plus:
score— a deterministic 0–100 affinity score. It is code-computed, not model-generated; items are returned in descending score order.matchReason— a short deterministic explanation; omitted when the scorer has no signal (e.g. cold-start).personaBound— the honest cold-start signal.falsemeans no persona or affinity is bound for this member yet, so ordering is affinity-neutral rather than a fabricated ranking. Bind a persona with/v1/personas/generateand drive affinity with events to move it totrue.
Scoring never overrides child-safety. The age gate excludes mature titles
regardless of affinity — a high-affinity mature title is still excluded for a
Child/Tween/Unknown member, and still counted in ageExcludedCount.
Flight-bound serves
If the session is bound to a flight (via flightInstanceId at session
setup) and that flight has a pushed active
catalog, the scored serve is intersected
with the flight’s active set — it returns only titles actually loaded on the
aircraft. The intersection can only ever narrow, and it never overrides
the age gate.
On a narrowed request, totalItems reports the narrowed basis: your
visible catalog intersected with the flight’s active set, before age
exclusion. ageExcludedCount counts removals from that same basis, so
totalItems − ageExcludedCount remains exactly the servable total.
Un-narrowed requests report unchanged numbers, so an integration that doesn’t
use flight binding sees no difference.
The unscored GET is never flight-bound. It accepts no session
locator, so there is no binding to resolve — it always returns the full
airline catalog, age-gated. Use the scored POST when you want the
flight’s active set.
Errors
Standard envelope. The GET returns 401 / 403 / 422 /
500; the POST additionally returns 400 session.unresolved when the
X-Session-Id header is missing or invalid (see
Sessions). Note that a
bad seatId is not an error on either verb — it fails closed to the
child-safe tier.