Passenger information capture in reservation sales

Passenger information capture in reservation sales can run through different paths depending on product configuration, check-in configuration, and selected trip data.

Please make sure you read the Conventions before continuing with this guide.

Prerequisites

You will need an X-API-KEY and a Basic Access Authentication token generated from your username and password.

What this guide solves

This guide explains how to decide, from API responses, which passenger-capture path to use when selling a reservation product:

  • Common passenger form (no dynamic form, no check-in capture)
  • Product dynamic form
  • Dynamic form + people lookup
  • Check-in capture during sale (simple and complex)
  • Check-in combined with dynamic forms

It also lists the endpoints and response properties you must inspect to make each decision.

Endpoints you need

Use these endpoints in your decision flow:

  1. GET https://api.betterez.com/inventory/products/{productId}
  • Purpose: detect if the selected product uses a passenger dynamic form.
  • Check: product.dynamicForms (first entry usually used by sales flow).
  1. Provider check-in configuration source
  • Integrator-friendly source: user login response from:
    • POST https://api.betterez.com/accounts/users
  • In that response, read:
    • provider.preferences.checkIn
  • The old shorthand GET /accounts/:providerId is not a public external endpoint in this context.
  • Internal/private equivalent exists as:
    • GET https://api.betterez.com/accounts/accounts/{id} (private/internal use).
  • Fields needed for decisioning:
    • provider.preferences.checkIn.enable
    • provider.preferences.checkIn.captureDuringSale
    • provider.preferences.checkIn.checkInFlow (simple or complex)
    • provider.preferences.checkIn.salesFlowFields.properties
    • provider.preferences.checkIn.salesFlowDynamicFormId
  1. GET https://api.betterez.com/inventory/trips (trip search)
  • Purpose: detect whether selected trips require check-in capture.
  • Check in selected departure and return trips:
    • segments[].requiresCheckIn
  1. GET https://api.betterez.com/accounts/renderable-dynamic-forms/{dynamicFormId}
  • Purpose: load render-ready dynamic form context when a product dynamic form is configured.
  • Check:
    • dynamicForm.definition
    • dynamicForm.mappedStandardBtrzFields
    • dynamicForm.mappingSeparators
    • captureFields.properties
    • dynamicFormsGrouppedFields
    • peopleLookupEnabled (product dynamic form use case)
    • peopleLookupFields (product dynamic form use case)
    • peopleLookupTrigger (product dynamic form use case)
  1. GET https://api.betterez.com/accounts/dynamic-forms/{dynamicFormId}
  • Purpose: load raw dynamic form definition for check-in complex flow when you specifically need the non-renderable document.
  1. GET https://api.betterez.com/accounts/people-lookups (dynamic-form path)
  • Purpose: fetch a person profile for dynamic-form sales flows.
  • Typical filters: documentTypeId, documentNumber, optionally dynamicFormId, providerId.
  • Response to inspect: people[].
  1. POST https://api.betterez.com/accounts/people-lookups or PUT https://api.betterez.com/accounts/people-lookups/{personId} (dynamic-form path)
  • Purpose: create/update lookup records and persist data mapped from dynamic forms.
  1. GET https://api.betterez.com/operations/passenger-check-in-info (check-in path)
  • Purpose: fetch existing check-in information by document data in check-in flows.
  • Required query: documentType, documentNumber.
  • Optional query: providerId.
  • Response to inspect: passengerCheckInInfos[].

Decision model (runtime)

Use this order of evaluation after selecting the product and searching/selecting trips:

  1. Evaluate whether check-in capture is enabled for sale.
  2. Evaluate whether at least one selected trip segment requires check-in.
  3. If both are true, use check-in passenger capture.
  4. Otherwise, evaluate product dynamic form.
  5. If product has dynamic form, use dynamic form flow.
  6. If neither applies, use common/basic passenger capture.

The check-in path has priority over the product dynamic-form path when requiresCheckIn is true in selected trips.

Case 1: Common passenger capture (baseline)

Use this when:

  • Product has no passenger dynamic form (product.dynamicForms empty or no usable id), and
  • Check-in capture path is not active for selected trips.

Behavior:

  • Capture standard passenger fields in the reservation payload sent to POST https://api.betterez.com/sales/cart.
  • Passenger object format (common flow, no dynamic form/check-in override):
    • Minimum practical fields:
      • fareId (string, ObjectId)
      • firstName (string)
      • lastName (string)
    • Fields that can actually carry user/business data in common flow:
      • firstName (string)
      • lastName (string)
      • email (string|null)
      • comments (string)
      • fare (string)
      • fareId (string)
      • fareIds (object)
      • fareClassIds (object)
      • ssrs (array)
      • items (array)
      • seats (array)
      • extra (object, includes gender and folios)
      • syncEntryId (string)
      • extraInput (string)
  • Example passenger object:
{
  "fareId": "665f5d9f3f3b4b0012345678",
  "fare": "Adult",
  "fareIds": {"outbound": "665f5d9f3f3b4b0012345678"},
  "fareClassIds": {"outbound": ""},
  "ssrs": [],
  "items": [],
  "seats": [],
  "firstName": "Jane",
  "lastName": "Doe",
  "email": null,
  "comments": "",
  "extra": {"gender": "female", "folios": []},
  "syncEntryId": "a8f9e7b2-9fcb-4f0f-b4f4-11b8d60c1d0a",
  "extraInput": ""
}
  • Do not call people lookup as a mandatory step.
  • Do not use check-in-specific lookup endpoint.

Case 2: Product dynamic form (without people lookup)

Use this when:

  • Product has dynamic form id, and
  • Check-in capture path is not active for selected trips.

Behavior:

  1. Read product.dynamicForms[0]._id.
  2. Call GET https://api.betterez.com/accounts/renderable-dynamic-forms/{dynamicFormId}.
  3. Build your passenger UI from captureFields.properties and dynamicFormsGrouppedFields.
  4. Map data to standard fields using dynamicForm.mappedStandardBtrzFields and dynamicForm.mappingSeparators.

If peopleLookupEnabled is false or missing, you can skip people lookup endpoints.

Case 3: Product dynamic form with people lookup

Use this when:

  • Product dynamic form path is active, and
  • dynamicForm.peopleLookupEnabled is true in the renderable response.

Behavior:

  1. Use peopleLookupFields and peopleLookupTrigger to decide when to query lookup.
  2. Call GET https://api.betterez.com/accounts/people-lookups with the configured lookup keys.
  3. If people[] has data, prefill passenger fields.
  4. If no result, collect data and create/update lookup profile:
    • POST https://api.betterez.com/accounts/people-lookups, or
    • PUT https://api.betterez.com/accounts/people-lookups/{personId}.
  5. Keep peopleLookupId per passenger to reuse in cart payloads.

Case 4: Check-in capture during sale

Check-in capture is active only when all of the following are true:

  • provider.preferences.checkIn.enable is true
  • provider.preferences.checkIn.captureDuringSale is true
  • Check-in has configured fields for sales flow (provider.preferences.checkIn.salesFlowFields.properties not empty or provider.preferences.checkIn.salesFlowDynamicFormId present)
  • At least one selected trip segment has requiresCheckIn = true

If any condition fails, do not use check-in capture as the passenger form for that selection.

Simple vs complex check-in flow

Use provider.preferences.checkIn.checkInFlow to understand configuration mode:

  • simple

    • Uses provider default check-in configuration.
    • No custom advanced configuration should be expected from integrator perspective.
  • complex

    • Sales check-in fields can be customized through:
      • salesFlowFields (JSON schema-like definition), or
      • salesFlowDynamicFormId (check-in dynamic form definition loaded from dynamic-forms API).

For complex, if salesFlowDynamicFormId exists, prefer loading that dynamic form to build check-in field definitions.

Check-in lookup endpoint

For check-in forms, use:

  • GET https://api.betterez.com/operations/passenger-check-in-info?documentType=...&documentNumber=...&providerId=...

Then:

  • If passengerCheckInInfos[0] exists, prefill check-in capture fields.
  • If empty, continue with manual capture.

This is different from product dynamic-form people lookup, which uses https://api.betterez.com/accounts/people-lookups.

Case 5: Check-in combined with dynamic forms

There are multiple combinations to consider.

Combination A: Product dynamic form + check-in capture

When both are configured, selected-trip requirement decides which one drives passenger capture:

  • If any selected segment has requiresCheckIn = true, use check-in capture.
  • If no selected segment requires check-in, use product dynamic form.

This means the same product can use different passenger-capture UIs depending on trip selection.

Combination B: Complex check-in using salesFlowDynamicFormId

In this case, check-in itself is built from a dynamic-form definition (type check-in configuration), but runtime behavior is still check-in flow:

  • Trigger remains requiresCheckIn + provider check-in sale settings.
  • Lookup endpoint remains https://api.betterez.com/operations/passenger-check-in-info.
  • Do not treat this as product passenger dynamic-form people-lookup flow unless your own business rule explicitly combines both.

Combination C: Check-in plus additional paid-in item dynamic forms

If your sale includes paid-in item forms, these can coexist with check-in capture:

  • Passenger capture stays in check-in flow (when required by selected segments).
  • Additional item-level forms may still use dynamic-form + people-lookup behavior per item form configuration.

Treat passenger-level check-in and item-level dynamic forms as separate capture layers.

Backoffice UI examples (betterez-app/app/modules/sales-reservations-2):

  1. Check-in form confirms passengers and then persists paid-in item lookups before cart completion.

    • UI behavior:
      • When check-in form is active and paidInDynamicFormFields exists, CheckInPaxFormService.confirmPassengers() calls createOrUpdateLookupPaidIn() and only then calls finishCart().
    • This means paid-in people lookup persistence is part of the check-in confirmation step, not a separate checkout stage.
  2. Paid-in dynamic form is detected from inventory items and loaded with its own definition.

    • UI behavior:
      • getPaidInDynamicForm() picks the first item that matches type === "paid_in", delayAttachToTicket === true, and has dynamicForm._id.
      • It then loads dynamic-form metadata (dynamicFormId, dynamicFormDefinition, lookup flags) specifically for item-level capture.
  3. Paid-in people lookup create/update uses https://api.betterez.com/accounts/people-lookups with dynamicForms payload.

    • UI behavior:
      • For each selected paid-in item, the UI checks existing people lookup by document data.
      • If found, it updates; if not, it creates.
    • Payload shape used by the UI for each paid-in lookup upsert:
{
  "person": {
    "dynamicForms": {
      "{{paidInDynamicFormId}}": {
        "documentType": "{{documentTypeId}}",
        "documentNumber": "{{documentNumber}}",
        "...": "other paid-in dynamic form fields"
      }
    }
  }
}
  1. After lookup upsert, UI maps returned person data back into item formData.
    • UI behavior:
      • Returned person.dynamicFormsFields values are copied into passengers[].items[].formData.
      • peopleLookupId is stored at the item form level for later cart submission consistency.

Cart payload patterns (what to send)

All cases below are sent to:

  • POST https://api.betterez.com/sales/cart

For complete cart body context, see:

A) Dynamic form flow payload

Use this when product dynamic form is active and check-in is not active for selected trips.

Key points:

  • Set items.reservation[0].dynamicFormId.
  • In each passenger, include standard fields.
  • If people lookup is used, include peopleLookupId per passenger.
{
  "items": {
    "reservation": [
      {
        "productId": "{{productId}}",
        "from": {"_id": "{{originStationId}}"},
        "to": {"_id": "{{destinationStationId}}"},
        "selectedTrips": {"departureTripId": "{{departureTripId}}", "returnTripId": ""},
        "roundTrip": false,
        "dateFrom": "{{YYYY-MM-DD}}",
        "dynamicFormId": "{{dynamicFormId}}",
        "passengers": [
          {
            "fareId": "{{fareId}}",
            "firstName": "Jane",
            "lastName": "Doe",
            "documentTypeId": "{{documentTypeId}}",
            "documentNumber": "A1234567",
            "email": "jane@example.com",
            "phone": "+14165550101",
            "peopleLookupId": "{{peopleLookupId}}",
            "ssrs": [],
            "seats": []
          }
        ]
      }
    ]
  }
}

B) Check-in flow payload

Use this when check-in is active and selected trip segments include requiresCheckIn = true.

Key points:

  • Send passenger capture in passengers[].checkInInfo.
  • Do not set reservation dynamicFormId for passenger capture in this case.
  • Keep standard passenger fields (firstName, lastName, documentTypeId, documentNumber, etc.) for compatibility and downstream processes.
{
  "items": {
    "reservation": [
      {
        "productId": "{{productId}}",
        "from": {"_id": "{{originStationId}}"},
        "to": {"_id": "{{destinationStationId}}"},
        "selectedTrips": {"departureTripId": "{{departureTripId}}", "returnTripId": ""},
        "roundTrip": false,
        "dateFrom": "{{YYYY-MM-DD}}",
        "passengers": [
          {
            "fareId": "{{fareId}}",
            "firstName": "Jane",
            "lastName": "Doe",
            "documentTypeId": "{{documentTypeId}}",
            "documentNumber": "A1234567",
            "ssrs": [],
            "seats": [],
            "checkInInfo": {
              "documentType": "{{documentTypeId}}",
              "documentNumber": "A1234567",
              "dateOfBirth": "1992-03-14",
              "nationality": "CA"
            }
          }
        ]
      }
    ]
  }
}

C) Product dynamic form + check-in both configured

There is no single combined passenger payload in runtime.

  • If selected segments require check-in:
    • Use payload pattern B (checkInInfo), and do not use reservation dynamicFormId for passenger capture.
  • If selected segments do not require check-in:
    • Use payload pattern A (set reservation dynamicFormId, optionally peopleLookupId).

Practical decision checklist

For each reservation sale:

  1. Load product (https://api.betterez.com/inventory/products/{productId}).
  2. Load provider check-in preferences from user login response (POST https://api.betterez.com/accounts/users), using provider.preferences.checkIn.
  3. Search/select trips (https://api.betterez.com/inventory/trips) and inspect selected segments[].requiresCheckIn.
  4. Apply precedence:
    • Check-in active + requiresCheckIn => check-in flow.
    • Else product dynamic form => dynamic-form flow.
    • Else common flow.
  5. In dynamic-form flow:
    • Load dynamic form by id.
    • If peopleLookupEnabled, execute people lookup flow.
  6. In check-in flow:
    • Resolve fields from configured sales-flow definition.
    • Use passenger check-in info lookup endpoint for prefill.

Notes and implementation recommendations

  • Always evaluate decisions after trip selection, not only after product selection, because requiresCheckIn is trip-dependent.
  • In round trips, evaluate departure and return segments; if either side requires check-in, choose check-in flow.
  • Keep lookup flows distinct:
    • Dynamic-form person profiles => https://api.betterez.com/accounts/people-lookups
    • Check-in passenger history => https://api.betterez.com/operations/passenger-check-in-info
  • When check-in is complex and field definitions are customized, rely on configured schema/dynamic-form definitions instead of hardcoding field lists.