> 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.

# Session Setup

`POST /v1/personas/session` binds the **seat roster** for a passenger session:
who is on which seat, and each member's declared age band. This is what gives a
member their real [age tier](/guides/sessions#age-tiers) — until you bind them,
every member resolves to `Unknown` and is served the child-safe catalog. Do it
once, early in the session, before scored serves or suggestions.

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

| Field                                       | Required    | Rules                                                                                                                                                                                                                                                                                                       |
| ------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `members`                                   | yes         | 1–20 entries. Empty or > 20 → `422 request.validation`.                                                                                                                                                                                                                                                     |
| `members[].seatId`                          | yes         | Non-blank.                                                                                                                                                                                                                                                                                                  |
| `members[].ageBand`                         | yes         | Non-blank declared band — e.g. `"adult"`, `"8-12"`, `"under-5"`. A **binding input**, not the authoritative tier.                                                                                                                                                                                           |
| `members[].memberSlot`                      | no          | Which member on a shared seat. Omit for a solo traveler.                                                                                                                                                                                                                                                    |
| `members[].archetypeId`                     | no          | Optional starting persona — an `archetypeId` from [`GET /v1/personas`](/guides/personas#list).                                                                                                                                                                                                              |
| `members[].roleInGroup`                     | no          | Optional descriptor.                                                                                                                                                                                                                                                                                        |
| `members[].gender`                          | no          | `male`, `female`, or `determine`, case-insensitive; null/omitted = unspecified. Other values → `422 request.validation`. See [gender determination](#gender-determination).                                                                                                                                 |
| `members[].firstName`, `members[].lastName` | conditional | Both required for `gender: "determine"`; otherwise omit them. Each at most 128 characters, without control characters. Invalid values → `422 request.validation`; errors name the field without echoing the value.                                                                                          |
| `members[].locale`                          | no          | Preferred-language tag: 2–8 ASCII letters, optionally followed by subtags of 1–8 ASCII letters or digits separated by `-` or `_`; at most 35 characters. Malformed values → `422 request.validation`. A well-formed but unrecognized language supplies no language preference. Null/omitted = unspecified.  |
| `members[].cabinClass`                      | no          | Non-blank token, at most 64 characters, without control characters; otherwise `422 request.validation`. Stored for the session, with no ranking or recommendation effect. Null/omitted = unspecified.                                                                                                       |
| `flightInstanceId`                          | no          | Top-level (not per member). Binds the session to a [registered flight](/guides/flights). 1–128 characters, non-blank, no control characters — otherwise `422 request.validation`. The message names the field and never echoes your value back. **Read the replace-in-full rule below before you omit it.** |

```bash
curl -X POST "https://api.staging.westiq.ai/v1/personas/session" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Session-Id: seat-12A-f3d9c1" \
  -H "Content-Type: application/json" \
  -d '{
        "members": [
          { "seatId": "12A", "ageBand": "adult" },
          { "seatId": "12A", "memberSlot": "slot-2", "ageBand": "8-12" }
        ],
        "flightInstanceId": "NH0007-2026-07-27"
      }'
```

```json
{
  "schemaVersion": "1",
  "correlationId": "req_0197c0a1b2c3d4e5f60718293a4b5c6d",
  "boundMembers": 2,
  "ageTiers": ["Adult", "Child"],
  "flightBindingApplied": true
}
```

* `boundMembers` is how many profiles were persisted.
* `ageTiers` is the server-resolved [tier](/guides/sessions#age-tiers) for each
  member, **in the same order as your `members` array**. It is echoed for your
  display only — every later serve / suggest / enrich call re-resolves the tier
  from the stored profile and never trusts this value back.

> **Note**
>
> **Age resolution is fail-closed.** An unknown, blank, or unparseable `ageBand`
> resolves to `Unknown` — treated as the most-restrictive `Child` tier — never a
> permissive default. A member you don't bind at all is also `Unknown`. So a
> `Child`/`Unknown` in `ageTiers` for someone you meant to be an adult means the
> `ageBand` string wasn't recognized — check it.

## Gender determination \[#gender-determination]

Declaring `gender: "determine"` requests a best-effort determination from both
declared names. By sending them, the caller asserts it is **authorized to submit**
the names for this purpose. The names are transit-only: never stored, never logged,
and never present in audit records. For this determination, the name is disclosed
to our AI infrastructure provider, including its safety-screening endpoint.

Only a member **resolved as Adult** is eligible. That resolution uses the
caller-declared `ageBand`: you are responsible for an accurate `ageBand`.
Declaring a child as adult can cause that child's name to be processed. Members
resolved as Child, Tween, or Unknown remain unspecified without their names being
sent for determination. Calls refused before determination do not send the names.

An uncertain, unavailable or failed model determination leaves gender unspecified;
the roster can still bind. The platform adopts a result only above its confidence
threshold. Confidence is not returned or stored, and there is no per-member
determination status in the response. Cancellation and platform infrastructure
failures can still fail the request. Declared `male` or `female` values continue
to bind as supplied. See [data handling](/security#gender-determination).

## Language preference and cabin class

`locale` supplies the member's language preference for
[language-aware search ranking](/guides/search#language-destination). A title
without matching language data receives no language adjustment. `cabinClass` is
captured for the session only; it does not influence ranking or recommendations.

## Binding the session to a flight \[#flight-binding]

Sending `flightInstanceId` binds this session to a [registered flight](/guides/flights). Once bound, and once that flight has an active
catalog pushed, the session's [search](/guides/search#flights), [scored serve](/guides/catalog-serve#flights), and [suggestions](/guides/suggestions)
results are narrowed to the titles actually loaded on the aircraft.

> **Warning**
>
> **Each call sets the flight context in full — it is never merged.**
>
> * Supplying `flightInstanceId` **replaces** whatever flight the session was
>   bound to.
> * **Omitting it removes** any binding an earlier call left on this session.
>
> If you re-run session setup mid-flight — to add a member, say — and leave
> the field out, you are *unbinding the flight*, and results widen back to the
> full airline catalog. Send `flightInstanceId` on every call that should stay
> bound.

`flightBindingApplied` (boolean, always present) reports the outcome:

* **`true`** — the flight outcome you asked for is in effect: bound to the id
  you supplied, or, when you omitted it, any earlier binding removed.
* **`false`** — the flight step did **not** take effect. The roster bind still
  succeeded and the response is still a `200`, but results are not narrowed to
  the flight you named — and a previously bound flight may still be narrowing
  them. **Retrying the call re-applies it.**

Treat `false` as "retry this call", not as a failed session setup. Everything
else in the response is valid.

## Errors

Standard [envelope](/guides/errors): `400 session.unresolved` (missing/invalid
`X-Session-Id`), `401` / `403` auth, `422 request.validation` (empty `members`,
more than 20, any member missing `seatId`/`ageBand`, invalid gender/name pairing,
malformed name, locale, cabin class, or `flightInstanceId`), `500`.