> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.westiq.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.westiq.ai/_mcp/server.

# Suggestions

`POST /v1/suggestions` takes a batch of titles you're about to show a member and
scores them against that member's bound persona and affinity — a fast-tier
"why this, for you" pass over content you already have on screen.

[Session-scoped](/guides/sessions#the-x-session-id-header) — **requires the
`X-Session-Id` header**. Body (max 64 KiB):

| Field        | Required | Rules                                                                                                                                                                                  |
| ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `seatId`     | yes      | The member's seat.                                                                                                                                                                     |
| `titleIds`   | yes      | 1–30 **integer** title ids from your catalog or search responses (see the [id note](/guides/sessions#the-x-session-id-header)). Unknown or nonservable ids are dropped before scoring. |
| `memberSlot` | no       | Which member on a shared seat. Omit for solo.                                                                                                                                          |

```bash
curl -X POST "https://api.staging.westiq.ai/v1/suggestions" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Session-Id: seat-12A-f3d9c1" \
  -H "Content-Type: application/json" \
  -d '{ "seatId": "12A", "titleIds": [2041, 2088, 3120] }'
```

```json
{
  "schemaVersion": "1",
  "correlationId": "req_0197c0a1b2c3d4e5f60718293a4b5c6d",
  "items": [
    {
      "titleId": 2041,
      "delta": 0.18,
      "boostScore": 74,
      "whyForYou": "Leans into the character drama you finish most"
    }
  ],
  "behavioralInsight": "You're in a settle-in, one-long-film mood.",
  "moodInference": "focused"
}
```

* `delta` and `boostScore` (0–100) are **code-clamped** composites — the model
  supplies a raw signal, deterministic code computes the final numbers.
* `whyForYou` and `behavioralInsight` are passenger-visible model free text.
* `moodInference` is optional and may be `null`.

## Flight-bound sessions \[#flights]

If the session is bound to a flight (via `flightInstanceId` at [session setup](/guides/session-setup)) and that flight has a [pushed active catalog](/guides/flights#active-catalog), suggestions are intersected with the
flight's active set — titles not loaded on the aircraft are not suggested.
The intersection can only ever **narrow**, and it never overrides the age gate
below. A session with no flight binding, or a flight with no push, behaves
exactly as before.

## Age-gating: an empty result is honest, not an error

Before any model call, the requested `titleIds` are filtered against the member's
[age tier](/guides/sessions#age-tiers). This has an important consequence:

> **Warning**
>
> **A fully-gated request returns an honest empty result, not a `4xx`.** If none
> of the titles you sent are allowed for this member's tier (e.g. a `Child`
> member and all-mature titles, or an unbound member whose tier is `Unknown`),
> the platform makes **no model call** and returns a `200` with empty fields:
>
> ```json
> { "schemaVersion": "1", "correlationId": "req_…",
>   "items": [], "behavioralInsight": "" }
> ```
>
> (`moodInference` is null here, and null fields are **omitted** from responses
> — so the key is simply absent, not sent as `null`.)
>
> Do **not** treat this as a failure — it is the fail-closed design, and it costs
> nothing. If you expected results, check that the member is
> [bound with the right age](/guides/session-setup) and that the `titleIds` suit
> their tier.

For **every age tier, including adults**, ids your airline's catalog cannot serve
are dropped before scoring and never appear in `items`. This includes unknown,
mistyped and other-airline ids. A mixed request still scores its eligible titles.
If every id is unknown or nonservable, the response is the same honest `200`
empty shape above: `items: []`, `behavioralInsight: ""`, and a null
`moodInference` omitted from the wire. No model call is made for that empty set.
Send ids obtained from your catalog or search responses.

Catalog titles with an unknown rating remain excluded for non-adult tiers.

> **Note**
>
> Today the result rides **this HTTP response only** — the
> [`suggestions.updated` SSE frame](/guides/session-events#frame-types) is not yet
> published. Read the response directly.

## Errors

Standard [envelope](/guides/errors): `400 session.unresolved`, `401` / `403`,
`422 request.validation` (blank `seatId`, empty `titleIds`, or more than 30),
`429 rate.limited` over the [per-client request-rate limit](/guides/errors#rate-limits) (honor the `Retry-After` header), `500`.