openapi: 3.0.1
info:
  title: BagID API — V2
  description: REST API for managing the digital baggage journey
  version: 0.1.52 (build unknown)
servers:
- url: http://localhost
  description: Generated server url
tags:
- name: Nonce Generation
  description: Generates a secure nonce to initialize passwordless authentication
- name: Device attachment
  description: V2 device attachment endpoints
- name: EBT
  description: V2 EBT endpoints
- name: Authentication
  description: Endpoints for authentication and certificate issuance
- name: Journey
  description: V2 journey endpoints
paths:
  /v2/journeys/{journeyId}/baggage-tags:
    post:
      tags:
      - Journey
      summary: Generate baggage tag for selected passengers
      description: Generates a bag tag for selected passengers in the specified journey. Provide lpn to target an existing slot by license plate, slotNumber (1-based) to target a slot by position (cancelling any existing cancellable LPN first), or omit both to use the next available slot. lpn and slotNumber are mutually exclusive.
      operationId: generateBagTag
      parameters:
      - name: journeyId
        in: path
        required: true
        schema:
          type: integer
          format: int32
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateBagTagRequest'
        required: true
      responses:
        '200':
          description: Bag tag generated; returns updated journey
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '401':
          description: Unauthorized - Missing or invalid Bearer token or app key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '400':
          description: Bad Request - Invalid journeyId, missing passengers, or both lpn and slotNumber provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '422':
          description: Unprocessable - LPN not cancellable or bag tag generation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
  /v2/journeys/{journeyId}/airline-baggages:
    post:
      tags:
      - Journey
      summary: Add airline-provided baggage to an existing journey
      description: Adds one baggage record to the journey using airline-app provided baggage data. Optionally send baggageAllowance (pieces) to update the journey allowance used in responses.
      operationId: addAirlineBaggage
      parameters:
      - name: journeyId
        in: path
        required: true
        schema:
          type: integer
          format: int32
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AirlineBaggageCreateRequest'
        required: true
      responses:
        '404':
          description: Not Found - Unknown journeyId
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '400':
          description: Bad Request - Invalid journeyId or malformed request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '401':
          description: Unauthorized - Missing or invalid Bearer token or app key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '422':
          description: Unprocessable - Business rule rejection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '201':
          description: Baggage added; returns updated journey
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
  /v2/journeys/from-bcbp:
    post:
      tags:
      - Journey
      summary: Load journey using BCBP scan data
      description: Loads journey details from BCBP scan payload. Requires Bearer token and X-Source-App-Key.
      operationId: loadFromBcbp
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JourneyAddWithBcbpRequest'
        required: true
      responses:
        '400':
          description: Bad Request - Invalid or missing request fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '200':
          description: Journey loaded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '401':
          description: Unauthorized - Missing or invalid Bearer token or app key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
        '422':
          description: Unprocessable - Invalid/unsupported BCBP or airline error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
  /v2/journeys/from-airline-payload:
    post:
      tags:
      - Journey
      summary: Create one journey per passenger from airline payload
      description: Creates or updates journeys from structured passenger/flight payload from the airline app (persisted directly; no DCS lookup). Optional per-passenger `baggageAllowance` (pieces) sets journey allowance; optional top-level `baggageAllowance` applies when omitted per passenger. Optional top-level `baggageAllowanceUnit` (e.g. PIECES, KG) is stored on the ticket. Embedded `baggage` on each passenger is persisted; `baggageSlots` lists one entry per stored bag. Requires Bearer token and X-Source-App-Key.
      operationId: loadFromAirlinePayload
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JourneyFromAirlinePayloadRequest'
        required: true
      responses:
        '400':
          description: Bad Request - Invalid or missing request fields
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/JourneyResponse'
        '422':
          description: Unprocessable - Payload rejected by business rules
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/JourneyResponse'
        '401':
          description: Unauthorized - Missing or invalid Bearer token or app key
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/JourneyResponse'
        '200':
          description: Journeys created/updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JourneyResponse'
  /v2/ebt-unlocks:
    post:
      tags:
      - EBT
      summary: Remove EBT custody lock (lock holder)
      operationId: ebtUnlocks
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EbtUnlockRequest'
        required: true
      responses:
        '403':
          description: Forbidden — not lock holder, unknown device, or missing scope
        '204':
          description: Lock removed or already absent
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
  /v2/ebt-lookups:
    post:
      tags:
      - EBT
      summary: EBT lock status for unique device id
      operationId: ebtLookups
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EbtLockLookupRequest'
        required: true
      responses:
        '200':
          description: Lock status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtLockLookupResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtLockLookupResponse'
        '403':
          description: Forbidden — missing scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtLockLookupResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtLockLookupResponse'
  /v2/ebt-custody-proofs:
    post:
      tags:
      - EBT
      summary: Custody proof (PNR and surname)
      operationId: ebtCustodyProofs
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EbtCustodyProofRequest'
        required: true
      responses:
        '429':
          description: Too many requests
        '403':
          description: Forbidden — wrong proof, no lock, or missing scope
        '400':
          description: Bad Request
        '204':
          description: Lock cleared
        '401':
          description: Unauthorized
  /v2/ebt-authorizations:
    post:
      tags:
      - EBT
      summary: Authorize EBT write for GUID and nonce
      operationId: ebtWriteAuthorizations
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EbtWriteAuthorizationRequest'
        required: true
      responses:
        '500':
          description: Signing failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtWriteAuthorizationResponse'
        '403':
          description: Forbidden — tag is locked by another journey or missing scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtWriteAuthorizationResponse'
        '200':
          description: Authorization token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtWriteAuthorizationResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtWriteAuthorizationResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EbtWriteAuthorizationResponse'
  /v2/device-attachments:
    post:
      tags:
      - Device attachment
      summary: Attach generated baggage to physical device
      description: 'Finalizes tag transfer: resolves the device by the same device payload as POST /v1/device/new. Lookup order for an existing device: uniqueId, then mac, then guid. Aborts with 400 if the device has an active journey lock row (custody). If there is no active lock, baggage is attached and the journey lock is upserted for this trip. For existing device rows, latest non-empty metadata from the request (e.g. firmware, battery, tag name) is persisted. Existing user–device pairing is left unchanged (including when the device belongs to another user); the travel plan user is only assigned to the device when the device currently has no owner. Requires Bearer token (with travelPlanId), X-Travel-Plan-Id, and X-Source-App-Key.'
      operationId: attachToDevice
      parameters:
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceAttachmentRequest'
        required: true
      responses:
        '401':
          description: Unauthorized - Missing/invalid Bearer token or app key
        '400':
          description: Bad Request - Invalid or missing request fields
        '204':
          description: Attached successfully
  /v2/devices/{deviceGuid}/tracking-consent:
    patch:
      tags:
      - Device tracking consent
      summary: Grant or update device tracking consent
      description: Requires Bearer token, X-Travel-Plan-Id, and X-Source-App-Key. Device must have baggage on a journey for the travel plan.
      operationId: grantOrUpdateDeviceTrackingConsent
      parameters:
      - name: deviceGuid
        in: path
        required: true
        schema:
          type: string
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrackingConsentPatchRequest'
        required: true
      responses:
        '204':
          description: Consent granted or updated
        '400':
          description: Invalid request or device not on active journey
        '401':
          description: Unauthorized
        '404':
          description: Device not found
        '500':
          description: Consent persistence failed
    delete:
      tags:
      - Device tracking consent
      summary: Withdraw device tracking consent
      description: Idempotent when no active consent exists. Requires Bearer token, X-Travel-Plan-Id, and X-Source-App-Key.
      operationId: withdrawDeviceTrackingConsent
      parameters:
      - name: deviceGuid
        in: path
        required: true
        schema:
          type: string
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      responses:
        '204':
          description: Consent withdrawn or none existed
        '400':
          description: Device not on active journey
        '401':
          description: Unauthorized
        '404':
          description: Device not found
  /v2/auth/refresh/sessions:
    post:
      tags:
      - Authentication
      summary: Refresh access token
      description: Uses a refresh token bound to the client and (optionally) user to issue a new short-lived access token. When rotation is enabled, the old refresh token is invalidated and a new one is returned. No Bearer token is required for this call.
      operationId: refreshSession
      parameters:
      - name: X-Source-App-Key
        in: header
        description: Client key identifying the partner application
        required: true
        schema:
          type: string
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshTokenRequest'
        required: true
      responses:
        '400':
          description: Bad Request - Missing or malformed refresh token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '403':
          description: Forbidden - Invalid or missing X-Source-App-Key header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Unauthorized - Refresh token invalid, expired, invalidated, or client mismatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '200':
          description: Token refresh successful; new access token (and optionally rotated refresh token) returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
  /v2/auth/login:
    post:
      tags:
      - Authentication
      summary: Login
      description: Authenticates a client using nonce and client-key. Validates the nonce (must be unused and not expired), consumes it, and returns a short-lived JWT access token (valid for ~10 minutes). The client-key is validated via the X-Source-App-Key header. Use this token as Bearer authorization on protected V2 routes such as /v2/auth/device-client-certificates, /v2/ebt-lookups, and /v2/ebt-authorizations.
      operationId: login
      parameters:
      - name: X-Source-App-Key
        in: header
        description: Client key identifying the partner application
        required: true
        schema:
          type: string
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NonceRequest'
        required: true
      responses:
        '403':
          description: Forbidden - Invalid or missing X-Source-App-Key header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '400':
          description: Bad Request - Missing or invalid request parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '200':
          description: Login successful, JWT token returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Unauthorized - Invalid nonce, expired nonce, or nonce already used
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
  /v2/auth/federated/sessions:
    post:
      tags:
      - Authentication
      summary: Login using host federated identity token
      description: Validates federated identity token (e.g. Apple Sign In, Google) and X-Source-App-Key. Resolves or creates user by email from the token and returns a short-lived JWT access token (valid for ~10 minutes).
      operationId: federatedLogin
      parameters:
      - name: X-Source-App-Key
        in: header
        description: Client key identifying the partner application
        required: true
        schema:
          type: string
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FederatedLoginRequest'
        required: true
      responses:
        '403':
          description: Forbidden - Invalid or missing X-Source-App-Key header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '400':
          description: Bad Request - Missing or invalid request parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '200':
          description: Login successful, JWT token returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Unauthorized - Invalid federated token or missing email in token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
  /v2/auth/device-client-certificates:
    post:
      tags:
      - Authentication
      summary: Issue X.509 client certificate
      description: 'Issues a firmware-compatible client certificate chain for BLE identity. Returns certificateChain: base64(intermediate_DER || leaf_DER) — decode base64 once, then parse two X.509 DER certificates in order. The caller uses the access token from login to call this endpoint, then presents the returned certificate during EBT BLE connect. Write authorization is requested separately via /v2/ebt-authorizations.'
      operationId: deviceClientCertificates
      parameters:
      - name: X-Source-App-Key
        in: header
        required: true
        schema:
          type: string
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceClientCertificateRequest'
      responses:
        '200':
          description: X.509 client certificate issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceClientCertificateResponse'
        '500':
          description: Certificate issuance failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceClientCertificateResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceClientCertificateResponse'
        '403':
          description: Forbidden — missing scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceClientCertificateResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceClientCertificateResponse'
  /v2/auth/nonce:
    get:
      tags:
      - Nonce Generation
      summary: Generate a nonce for authentication
      description: Generates a cryptographically secure, short-lived nonce (number used once) for app attestation during login. The nonce expires after 30-60 seconds and can only be used once. Requires a valid client-key header to identify the partner application.
      operationId: generateNonce
      parameters:
      - name: X-Source-App-Key
        in: header
        description: Client key identifying the partner application
        required: true
        schema:
          type: string
        example: partner-app-client-key-123
      - name: Accept
        in: header
        schema:
          type: string
          enum:
          - application/json
      responses:
        '401':
          description: Unauthorized - Invalid client-key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NonceResponse'
        '200':
          description: Nonce generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NonceResponse'
        '400':
          description: Bad Request - Missing or invalid client-key, or rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NonceResponse'
        '500':
          description: Internal Server Error - Failed to generate nonce
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NonceResponse'
  /v2/auth/nonce/health:
    get:
      tags:
      - Nonce Generation
      summary: Health check for nonce service
      description: Simple health check endpoint to verify nonce service availability
      operationId: health
      responses:
        '200':
          description: OK
          content:
            '*/*':
              schema:
                type: string
components:
  schemas:
    DeviceModel:
      type: object
      properties:
        ccid:
          type: string
        guid:
          type: string
        mac:
          type: string
        tagName:
          type: string
        phoneMac:
          type: string
        bagIDModel:
          type: string
          enum:
          - bagid_2
          - bagid_2_st
          - bagid_go
        batteryPercentage:
          type: integer
          format: int32
        firmwareVersion:
          type: string
        serialNumber:
          type: string
        uniqueID:
          type: string
        hubbleDeviceId:
          type: string
          description: Optional opaque Hubble Network device identity used on location pings.
    GenerateBagTagRequest:
      required:
      - passengerList
      type: object
      properties:
        passengerList:
          type: array
          description: Passengers to generate bag tags for
          items:
            $ref: '#/components/schemas/PassengerDTO'
        lpn:
          type: string
          description: Existing license plate number to target. Mutually exclusive with slotNumber.
        slotNumber:
          type: integer
          description: 1-based slot index to target. If the slot contains a cancellable LPN it will be cancelled before the new tag is created. Mutually exclusive with lpn.
          format: int32
      description: Request to generate a baggage tag for selected passengers in a journey
    PassengerDTO:
      type: object
      properties:
        passengerId:
          type: integer
          format: int32
        givenName:
          type: string
        surname:
          type: string
        title:
          type: string
        baggageGroupId:
          type: string
        selected:
          type: boolean
        baggageAllowance:
          type: integer
          format: int32
        boardingPass:
          type: string
    BaggageDTO:
      type: object
      properties:
        baggageId:
          type: integer
          format: int32
        bagtagHumanReadable:
          type: string
        licenseplate:
          type: string
        greenstrip:
          type: boolean
        priority:
          type: boolean
        createdByBagId:
          type: boolean
        deviceId:
          type: integer
          format: int32
        trackingId:
          type: integer
          format: int32
        uniqueBagIdentifier:
          type: string
        specialRequirement:
          type: string
      description: Baggage in this slot, or null if empty
    BaggageSlotDTO:
      required:
      - index
      type: object
      properties:
        index:
          type: integer
          description: Slot index
          format: int32
        baggage:
          $ref: '#/components/schemas/BaggageDTO'
      description: Baggage slot with optional baggage
    JourneyFlightDTO:
      type: object
      properties:
        flightId:
          type: string
        sequence:
          type: integer
          description: Flight sequence in journey
          format: int32
        boardingTime:
          type: string
          format: date-time
        scheduledDepartureTime:
          type: string
          format: date-time
        departureDate:
          type: string
        departureTz:
          type: string
        updatedScheduledDepartureTime:
          type: string
          format: date-time
        scheduledArrivalTime:
          type: string
          format: date-time
        destinationTz:
          type: string
        updatedScheduledArrivalTime:
          type: string
          format: date-time
        departureCity:
          type: string
        departureAirport:
          type: string
        destinationCity:
          type: string
        destinationAirport:
          type: string
        classOfTravel:
          type: string
        beltNumber:
          type: string
        status:
          type: string
        gate:
          type: string
        checkedIn:
          type: boolean
          description: Whether the passenger is checked in on this segment; persisted on the flight when sent on from-airline-payload.
      description: Flight in journey
    JourneyResponse:
      type: object
      properties:
        recordLocator:
          type: string
        journeyId:
          type: integer
          format: int32
        passenger:
          $ref: '#/components/schemas/PassengerDTO'
        baggageSlots:
          type: array
          items:
            $ref: '#/components/schemas/BaggageSlotDTO'
        flights:
          type: array
          items:
            $ref: '#/components/schemas/JourneyFlightDTO'
        totalAllowance:
          type: integer
          format: int32
        ticketOpenForBagtag:
          type: boolean
        ticketOpenForCheckin:
          type: boolean
        version:
          type: integer
          format: int32
        groupBooking:
          type: boolean
        ticketType:
          type: string
        checkInWarning:
          type: string
        classOfTravel:
          type: string
        carrier:
          type: string
        destinationFlightNumber:
          type: string
        destinationDate:
          type: string
          format: date-time
        destinationTz:
          type: string
        destinationAirport:
          type: string
        destinationCity:
          type: string
        firstTransferFlightNumber:
          type: string
        firstTransferDate:
          type: string
          format: date-time
        firstTransferAirport:
          type: string
        secondTransferFlightNumber:
          type: string
        secondTransferDate:
          type: string
          format: date-time
        secondTransferAirport:
          type: string
        departureAirport:
          type: string
        boardingTime:
          type: string
          format: date-time
        scheduledArrival:
          type: string
          format: date-time
        arrivalTz:
          type: string
        scheduledDeparture:
          type: string
          format: date-time
        departureTz:
          type: string
        checkInWindowHours:
          type: integer
          format: int32
        completedOn:
          type: string
          format: date-time
        departed:
          type: boolean
        passengerName:
          type: string
        baggageAllowanceUnit:
          type: string
          description: 'Unit for the baggage allowance: KG, LB, or PIECES. Null when unknown.'
        passiveTagsEnabled:
          type: boolean
          description: True when bag tags are passive and do not consume the baggage allowance. Always true for weight-based allowances (KG, LB). Null when not resolved (e.g. company context not available).
        baggageCancellationAllowed:
          type: boolean
          description: True when existing bag tags in a slot can be cancelled via BagID before generating a replacement. Null when not resolved.
        validations:
          type: array
          description: Reasons why bag tag or check-in is not available for this journey. Empty when all actions are available.
          items:
            $ref: '#/components/schemas/JourneyValidation'
      description: Journey loaded from BCBP scan
    JourneyValidation:
      type: object
      properties:
        code:
          type: string
          description: Machine-readable validation code
          example: NOT_CHECKED_IN
          enum:
          - NO_FLIGHTS
          - JOURNEY_COMPLETED
          - NOT_CHECKED_IN
          - NO_DEPARTURE_DATE
          - DEPARTED
          - OUTSIDE_CHECK_IN_WINDOW
        message:
          type: string
          description: Human-readable explanation for the API consumer to present to the user
      description: A reason explaining why a journey action is not available
    AirlineBaggageCreateRequest:
      required:
      - airline
      - baggageTagNumber
      - destinationAirport
      type: object
      properties:
        baggageTagNumber:
          type: string
        airline:
          type: string
        destinationAirport:
          type: string
        baggageAllowance:
          minimum: 0
          type: integer
          description: Optional. Total checked-bag allowance (pieces) for this journey; updates stored allowance when provided.
          format: int32
      description: Airline-provided baggage information for journey creation payload
    JourneyAddWithBcbpRequest:
      required:
      - scanData
      type: object
      properties:
        scanData:
          type: string
          description: BCBP scan payload (e.g. from boarding pass)
        airline:
          type: string
          description: Optional airline code hint for decoding
      description: BCBP scan data (e.g. boarding pass 'M1DOE/JOHN            EXYZ123 ZRHSFOBA 1234 063F035A0001 100') and optional airline hint
    JourneyFromAirlinePassengerPayload:
      required:
      - flights
      - passenger
      - pnr
      - surname
      type: object
      properties:
        passenger:
          $ref: '#/components/schemas/PassengerDTO'
        pnr:
          type: string
        surname:
          type: string
        flights:
          type: array
          items:
            $ref: '#/components/schemas/JourneyFlightDTO'
        baggage:
          type: array
          items:
            $ref: '#/components/schemas/AirlineBaggageCreateRequest'
        baggageAllowance:
          minimum: 0
          type: integer
          description: Optional. Total checked-bag allowance (pieces) for this journey; overrides passenger.baggageAllowance when provided.
          format: int32
      description: Per-passenger journey payload received from airline app
    JourneyFromAirlinePayloadRequest:
      required:
      - passengers
      type: object
      properties:
        passengers:
          type: array
          items:
            $ref: '#/components/schemas/JourneyFromAirlinePassengerPayload'
        baggageAllowanceUnit:
          type: string
          description: Optional. Allowance unit for the ticket, e.g. KG, LB, PIECES.
        baggageAllowance:
          minimum: 0
          type: integer
          description: Optional. Default total checked-bag allowance (pieces) when not set per passenger.
          format: int32
      description: Journey creation payload from airline app
    EbtUnlockRequest:
      required:
      - uniqueDeviceId
      type: object
      properties:
        uniqueDeviceId:
          type: string
    EbtLockLookupRequest:
      required:
      - uniqueDeviceId
      type: object
      properties:
        uniqueDeviceId:
          type: string
    EbtLockHints:
      type: object
      properties:
        maskedRecordLocator:
          type: string
        maskedSurname:
          type: string
        departureAirport:
          type: string
        departureAt:
          type: string
          format: date-time
        destinationAirport:
          type: string
    EbtLockLookupResponse:
      type: object
      properties:
        locked:
          type: boolean
        sameTravelPlan:
          type: boolean
        requiresCustodyProof:
          type: boolean
        hints:
          $ref: '#/components/schemas/EbtLockHints'
    EbtCustodyProofRequest:
      required:
      - recordLocator
      - surname
      - uniqueDeviceId
      type: object
      properties:
        uniqueDeviceId:
          type: string
        recordLocator:
          type: string
        surname:
          type: string
    EbtWriteAuthorizationRequest:
      required:
      - guid
      - nonce
      type: object
      properties:
        guid:
          type: string
          description: Device UUID (RFC 4122 string or 32 hex digits). Hashed as 16-byte GUID for the token digest.
        nonce:
          type: string
          description: 64-character hex encoding of the 32-byte SEC nonce from the tag.
    EbtWriteAuthorizationResponse:
      type: object
      properties:
        transactionToken:
          type: string
        algorithm:
          type: string
        expiresAt:
          type: string
          format: date-time
    DeviceAttachmentRequest:
      required:
      - baggageId
      - device
      type: object
      properties:
        device:
          $ref: '#/components/schemas/DeviceModel'
        baggageId:
          minimum: 1
          type: integer
          format: int32
        trackingConsent:
          $ref: '#/components/schemas/TrackingConsentRequestFields'
    TrackingConsentRequestFields:
      type: object
      description: Optional consent to record after a successful attach. When trackingConsentGranted is true, consentType is required.
      properties:
        trackingConsentGranted:
          type: boolean
          description: When true, records tracking consent after attach succeeds.
        consentType:
          $ref: '#/components/schemas/TrackingConsentType'
        consentGrantedAt:
          type: string
          format: date-time
        consentWindowStartsAt:
          type: string
          format: date-time
        consentWindowEndsAt:
          type: string
          format: date-time
        extendedHours:
          type: integer
          format: int32
    TrackingConsentPatchRequest:
      type: object
      required:
      - trackingConsentGranted
      - consentType
      properties:
        trackingConsentGranted:
          type: boolean
          description: Must be true on PATCH; use DELETE to withdraw.
        consentType:
          $ref: '#/components/schemas/TrackingConsentType'
        consentGrantedAt:
          type: string
          format: date-time
        consentWindowStartsAt:
          type: string
          format: date-time
        consentWindowEndsAt:
          type: string
          format: date-time
        extendedHours:
          type: integer
          format: int32
    TrackingConsentType:
      type: string
      enum:
      - JOURNEY_PERIOD
      - EXTENDED_HOURS
      - SPECIFIED_PERIOD
    RefreshTokenRequest:
      type: object
      properties:
        refreshToken:
          type: string
    LoginResponse:
      type: object
      properties:
        accessToken:
          type: string
        tokenType:
          type: string
        expiresIn:
          type: integer
          format: int32
        refreshToken:
          type: string
    NonceRequest:
      required:
      - nonce
      type: object
      properties:
        nonce:
          type: string
    FederatedLoginRequest:
      required:
      - token
      type: object
      properties:
        token:
          type: string
    DeviceClientCertificateRequest:
      type: object
      properties:
        csr:
          type: string
    DeviceClientCertificateResponse:
      required:
      - certificateChain
      type: object
      properties:
        certificateChain:
          type: string
          description: 'Base64-encoded X.509 certificate chain for BLE. Decode base64 once to raw bytes, then parse two consecutive DER certificates in order: (1) intermediate CA certificate, (2) SDK client leaf certificate. Wire format: base64( intermediate_DER || leaf_DER ). Do not concatenate separate base64 strings; do not use PEM. Intermediate is signed offline by the root CA; leaf is signed online by ebt-transaction-signing-key.'
      description: Device client certificate chain for EBT BLE authentication
    NonceResponse:
      type: object
      properties:
        nonce:
          type: string
        expires_at:
          type: string
          format: date-time
        ttl_seconds:
          type: integer
          format: int32
