openapi: 3.0.1
info:
  title: Hubby API
  version: 2.1.0
  description: >
    Welcome to the Hubby API documentation. This API enables partners to
    seamlessly integrate eSIM booking and management capabilities into their
    applications.


    Key Features:

    - Create and manage eSIM bookings for your customers

    - Access our global package catalog with country-specific offerings

    - Track booking statuses and package activations

    - Support for multiple package types (starter, data-limited, unlimited,
    time-limited)


    ## Package Types


    The API supports four types of packages:


    - **Starter packages**: Hybrid packages that are both data and time limited.
    They provide a small data allowance within a short time period (default: 2
    days). Perfect for first-time users and trials.


    - **Data-limited packages**: Traditional packages with a specific data
    allowance that expires after a certain period (default: 365 days). This is
    the primary package type and the default for most use cases.


    - **Unlimited packages**: Packages that provide unrestricted data usage for
    a specified duration. Subject to fair use policy. Ideal for heavy data users
    and digital nomads.


    - **Time-limited packages**: Packages that provide a fixed data allowance
    for a specific duration with full-speed access. These packages expire when
    either the data limit or time limit is reached.


    **Note**: Top-ups are always data-limited packages, regardless of the
    original package type.


    Authentication:

    All API requests must include the following headers:

    - x-api-key: Your public API key

    - x-timestamp: Current Unix timestamp in milliseconds

    - x-signature: HMAC-SHA256 signature


    The HMAC signature must be generated for each request using:

    1. Concatenate: timestamp + HTTP method + request path
       Example: "1678901234GET/bookings"
    2. Generate HMAC-SHA256 using your secret key

    3. Convert to hex string


    Note: Swagger UI cannot be used to test the API directly as each request
    requires a unique HMAC signature.

    Please implement the authentication in your client application.


    Example Node.js Implementation:

    ```javascript

    const cryptoJs = require('crypto-js');


    // Configuration values that would normally come from environment

    const secretKey = "YOUR_API_SECRET";

    const publicKey = "YOUR_API_KEY";

    const baseUrl = "YOUR_BASE_URL";


    // Function to generate headers for API request

    function generateApiHeaders(method, path) {
        //Timestamp is in milliseconds e.g. 1715558400000
        const timestamp = Math.floor(Date.now()).toString();

        // Ensure url is a string
        let path = String(url);

        // Remove baseUrl from the url if present
        path = processedUrl.replace(baseUrl, '');

        // Create query string if needed
        const queryString = new URL(url).search;
        if (queryString) {
            processedUrl += queryString;
        }

        // Validate public key
        if (!publicKey) {
            throw new Error("Public key is required");
        }

        // Create the payload
        // Sample payload: 1715558400000GET/bookings?bookingId=1234567890
        const payload = timestamp + method + path;

        // Generate the HMAC signature
        const signature = cryptoJs.HmacSHA256(payload, secretKey).toString(cryptoJs.enc.Hex);

        // Return headers object
        return {
            'x-timestamp': timestamp,
            'x-signature': signature,
            'x-api-key': publicKey,
            'Accept': 'application/json'
        };
    }

    ```


    Need Help?

    - Technical Support: support@hubbyesim.com
servers:
  - url: https://europe-west4-hubby-esim.cloudfunctions.net/api_v2
    description: Production server
paths:
  /bookings:
    get:
      tags:
        - Booking
      summary: List all bookings
      description: >
        Retrieves a paginated list of bookings for the authenticated partner.

        The response is always fresh and not cached (uses Cache-Control
        headers).
      parameters:
        - name: perPage
          in: query
          required: false
          schema:
            type: integer
            default: 10
          description: Number of records to return per page (minimum 1)
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: >-
            Cursor for pagination (use nextCursor/prevCursor from previous
            response)
      responses:
        '200':
          description: A paginated list of bookings
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Success message
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/BookingResponse'
                  pagination:
                    $ref: '#/components/schemas/PaginationMeta'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
    post:
      tags:
        - Booking
      summary: Create a new booking
      description: >-
        Creates a new booking with the specified details. The booking is
        identified by the `email` or `booking_id` parameter. When no package
        specifications are provided, the end user will be queried for the
        destination and size will be determined by the partner settings.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - departure_date
                - package_specifications
              properties:
                departure_date:
                  type: string
                  format: date-time
                  description: |
                    ISO 8601 departure date. Example: '2026-01-03'.
                return_date:
                  type: string
                  format: date-time
                  description: |
                    ISO 8601 return date. Must be after `departure_date`.
                    Optional if not provided by the partner.
                email:
                  type: string
                  format: email
                  description: |
                    Traveler's email. Required if `booking_id` is not provided.
                booking_id:
                  type: string
                  minLength: 3
                  description: |
                    Booking ID. Required if `email` is not provided.
                external_id:
                  type: string
                  minLength: 3
                  maxLength: 100
                  nullable: true
                  description: >
                    External ID Usable by your organization to further identify
                    the booking (optional).
                phone:
                  type: string
                  pattern: ^\+\d{1,3}\d{1,14}$
                  description: Phone number in E.164 format (e.g., +123456789).
                first_name:
                  type: string
                  minLength: 1
                  maxLength: 100
                last_name:
                  type: string
                  minLength: 1
                  maxLength: 100
                full_name:
                  type: string
                  minLength: 1
                  maxLength: 200
                title:
                  type: string
                  enum:
                    - mr.
                    - ms.
                    - mrs.
                    - miss
                    - dr.
                    - prof.
                pax:
                  type: integer
                  minimum: 1
                  description: Number of passengers.
                flight_number:
                  type: string
                  pattern: ^[a-zA-Z0-9]{1,10}$
                gender:
                  type: string
                  enum:
                    - M
                    - F
                    - O
                date_of_birth:
                  type: string
                  format: date-time
                  description: Date of birth in ISO 8601 format.
                locale:
                  type: string
                  minLength: 2
                  maxLength: 5
                  description: Locale or language code (e.g., "en", "nl").
                data:
                  type: object
                  description: Additional metadata about the booking.
                communication_options:
                  type: object
                  description: >
                    Optional communication preferences for sending messages. If
                    not provided, messages will not be sent.
                  properties:
                    should_send_message:
                      type: boolean
                      description: Indicates if messages should be sent.
                    channels:
                      type: array
                      items:
                        type: string
                        enum:
                          - EMAIL
                          - PUSH
                          - SMS
                          - WHATSAPP
                      description: List of communication channels.
                custom_branding:
                  type: string
                  nullable: true
                  description: >
                    Sets a specific visual style for the app experience. The
                    brand identifier must correspond to a brand configuration
                    that exists in the Hubby platform. Contact support to set up
                    custom branding for your organization.
                departure_location:
                  type: string
                  nullable: true
                  description: >
                    The departure location for the booking (e.g., airport code,
                    city name, or address). Used to provide context about where
                    the traveler is departing from.
                package_specifications:
                  type: array
                  minItems: 1
                  description: >
                    A list of package specifications. At least one entry is
                    required.

                    Each entry is resolved either by `package_id` (direct
                    reference) or by other properties (destination, size,
                    package_type, etc.); when `package_id` is present, other
                    fields in that entry are ignored for resolution.
                  items:
                    type: object
                    properties:
                      external_user_id:
                        type: string
                        description: >
                          Your own identifier for the traveler receiving this
                          package.


                          **⚠️ This field is required for [Hubby
                          WebView](/guides/webview) and [Native eSIM
                          Integration](/guides/native-api). Omit it for all
                          other integration types** — it is ignored in the
                          traditional booking flow (email-based delivery, promo
                          codes, etc.) and supplying it incorrectly will route
                          the package into the eSIM management flow.


                          - **With `external_user_id` (required for WebView and
                          Native):** The package is linked to the traveler's
                          universal eSIM. Use the same value across all
                          subsequent calls (redirect tokens, dashboard, redeem,
                          top-ups).

                          - **Without `external_user_id` (traditional flow):**
                          The package follows the standard booking flow. The
                          field is ignored.
                        example: user_abc123
                      destination:
                        oneOf:
                          - type: string
                            pattern: ^[A-Z]{2,3}$
                            description: >
                              Single destination code (ISO 2-3 uppercase
                              letters).

                              Example: 'US', 'NL', 'GRC', 'XA'.
                          - type: array
                            items:
                              type: string
                              pattern: ^[A-Z]{2,3}$
                            minItems: 1
                            description: >
                              Array of destination codes for multi-destination
                              bookings.

                              The system will optimize ESIM selection to cover
                              all destinations with the fewest ESIMs.

                              Example: ['US', 'CA', 'MX'] for North American
                              trip.
                        description: >
                          Destination code(s) for the booking. Can be a single
                          destination or an array of destinations for
                          multi-destination trips with ESIM optimization.
                          (optional)
                      size:
                        type: string
                        pattern: ^[0-9]\d*(\.\d+)?(MB|GB)$
                        description: >-
                          Package size (e.g., '500MB', '1GB', '3GB', '5GB',
                          '10GB', '20GB')
                      package_id:
                        type: string
                        description: Identifier for the package (optional).
                      package_type:
                        type: string
                        enum:
                          - starter
                          - data-limited
                          - unlimited
                          - time-limited
                        default: data-limited
                        description: >
                          Type of package to create. 

                          - `data-limited`: Traditional packages with specific
                          data allowance (default)

                          - `time-limited`: Unlimited data for a specific
                          duration

                          - `starter`: Both data and time limited packages
                      package_duration:
                        type: integer
                        minimum: 1
                        default: 365
                        description: >
                          Duration of the package in days. 

                          - Defaults to 365 days for data-limited packages

                          - Defaults to 2 days for starter and time-limited
                          packages
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Success message
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/BookingResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /bookings/{id}:
    get:
      tags:
        - Booking
      summary: Get booking details
      description: Retrieves details of a specific booking
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Booking ID
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Success message
                  data:
                    $ref: '#/components/schemas/BookingResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
    delete:
      tags:
        - Booking
      summary: Cancel booking
      description: >
        Cancels a specific booking by setting its status to `CANCELLED` and
        anonymizing all personal data fields.


        The following fields are hashed with MD5 using a configurable salt
        (`ANONYMIZATION_SALT` environment variable): `first_name`, `last_name`,
        `full_name`, `phone`, `flight_number`, `departure_location`. The `email`
        field is replaced with a non-identifiable address. Fields `title`,
        `gender`, and `pax` are cleared. The `booking_id` and `external_id` are
        preserved intact.


        The booking record is **not deleted** from the database.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Booking ID
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Success message
                  data:
                    type: object
                    description: Empty object
                    example: {}
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /bookings/{id}/byExternalBookingId:
    delete:
      tags:
        - Booking
      summary: Cancel booking by booking ID
      description: >
        Cancels a booking using its external booking ID by setting its status to
        `CANCELLED` and anonymizing all personal data fields.


        The following fields are hashed with MD5 using a configurable salt
        (`ANONYMIZATION_SALT` environment variable): `first_name`, `last_name`,
        `full_name`, `phone`, `flight_number`, `departure_location`. The `email`
        field is replaced with a non-identifiable address. Fields `title`,
        `gender`, and `pax` are cleared. The `booking_id` and `external_id` are
        preserved intact.


        The booking record is **not deleted** from the database.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: External booking ID (booking_id)
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Success message
                  data:
                    type: object
                    description: Empty object
                    example: {}
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /bookings/{booking_id}/updatePackageSpecifications:
    put:
      tags:
        - Booking
      summary: Update all promo codes for a booking
      description: >
        Updates the `package_specification` on **all** promo codes for the
        booking.

        Promo codes that have already been used are **skipped**; the response
        includes `updated_count` and `skipped_count`.


        **Authentication:** HMAC keys.
      parameters:
        - name: booking_id
          in: path
          required: true
          schema:
            type: string
          description: The external booking ID (e.g. `"BK-12345"`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - package_specification
              properties:
                package_specification:
                  $ref: '#/components/schemas/PackageSpecification'
      responses:
        '200':
          description: >
            Successfully updated package specifications. When some promo codes
            were skipped (already redeemed), `message` is `"Updated N promo
            code(s). Skipped M already redeemed."` and `data` includes both
            `updated_count` and `skipped_count`. When all promo codes were
            updated, `message` is `"Successfully updated package specification
            for booking."` and `skipped_count` is omitted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Updated 3 promo code(s). Skipped 1 already redeemed.
                  data:
                    $ref: >-
                      #/components/schemas/UpdatePackageSpecForPromoCodesResponse
        '400':
          description: |
            Validation failed or package not found.
            Messages: `"booking_id is required"`, `"Package not found"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: No partner ID resolved from auth. Message `"Partner ID not set!"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '403':
          description: Missing `bookings.update_package_specifications` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '404':
          description: Booking not found for the partner. Message `"Booking not found!"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '422':
          description: >-
            Body validation failed (Zod). Message `"Validation failed"` with
            issue details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '500':
          description: Server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /bookings/{booking_id}/updatePackageSpecification/{uuid}:
    put:
      tags:
        - Booking
      summary: Update a single promo code by UUID
      description: >
        Updates the `package_specification` on **only** the promo code whose
        `uuid` matches the path parameter.

        If that promo code is already redeemed, the API returns **400 Bad
        Request**.


        **Authentication:** HMAC keys.
      parameters:
        - name: booking_id
          in: path
          required: true
          schema:
            type: string
          description: The external booking ID.
        - name: uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The promo code's UUID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - package_specification
              properties:
                package_specification:
                  $ref: '#/components/schemas/PackageSpecification'
      responses:
        '200':
          description: >-
            Successfully updated the single promo code. `updated_count` is
            always `1`; there is no `skipped_count`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Successfully updated package specification for booking.
                  data:
                    $ref: >-
                      #/components/schemas/UpdatePackageSpecForPromoCodesResponse
        '400':
          description: >
            Validation failed, invalid uuid, package not found, package not
            active, or promo code already redeemed.

            Messages: `"booking_id is required"`, `"Invalid uuid"`, `"Package
            not found"`, `"Promo code has already been redeemed and cannot be
            updated"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: No partner ID resolved from auth. Message `"Partner ID not set!"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '403':
          description: Missing `bookings.update_package_specifications` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '404':
          description: >
            Booking not found for the partner, or promo code UUID not found for
            this booking.

            Messages: `"Booking not found!"`, `"Promo code not found!"`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '422':
          description: >-
            Body validation failed (Zod). Message `"Validation failed"` with
            issue details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '500':
          description: Server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /destinations:
    get:
      tags:
        - Destination
      summary: List all destinations
      description: >
        Retrieves a list of all available destinations, including both
        individual countries and multi-country regions.

        Each destination includes localized names, ISO3 country codes, and
        metadata such as type and tier.
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Destinations fetched successfully.
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/DestinationResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /destinations/{id}:
    get:
      tags:
        - Destination
      summary: Get a destination
      description: >
        Retrieves a single destination by its ID, including its available
        bundles (data packages).

        Use this endpoint to get detailed information about a specific country
        or region and the eSIM packages available for it.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: >-
            The destination ID (ISO3 code for countries, e.g. "NLD", or region
            code, e.g. "XA")
          example: XA
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Destination fetched successfully.
                  data:
                    $ref: '#/components/schemas/DestinationDetailResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: Destination not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: Destination not found.
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /promoCodes/redeem:
    post:
      tags:
        - PromoCode
      summary: Redeem a promo code
      description: Redeem a promo code for a booking or eSIM
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - promo_code
              properties:
                promo_code:
                  type: string
                  description: The promo code to redeem.
      responses:
        '200':
          description: Successful promo code redemption
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Promo code redeemed successfully
                  data:
                    type: object
                    properties:
                      iccid:
                        type: string
                        nullable: true
                        description: >-
                          eSIM ICCID (null when eSIM lookup fails or returns
                          null)
                        example: '1234567890'
                      qr:
                        type: string
                        nullable: true
                        description: >-
                          eSIM QR code (null when eSIM lookup fails or returns
                          null)
                        example: https://example.com/qr-code
        '400':
          description: Invalid or missing promo code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: Promo code not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /promo-codes/{id}:
    get:
      tags:
        - PromoCode
      summary: Get promo code details
      description: |
        Retrieve details of a specific promo code by ID.

        **Authorization:**
        - Admins can access any promo code
        - Partners can only access promo codes belonging to their organization
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The promo code ID
      responses:
        '200':
          description: Promo code details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Promo code fetched successfully!
                  data:
                    type: object
                    properties:
                      promo_code:
                        type: string
                        description: The promo code
                      uuid:
                        type: string
                        format: uuid
                        nullable: true
                        description: Unique identifier for the claim link
                      claim_url:
                        type: string
                        format: uri
                        nullable: true
                        description: Direct link for users to claim the promo code
                      deep_link_url:
                        type: string
                        format: uri
                        nullable: true
                        description: >-
                          Mobile deep link that opens the Hubby app and
                          auto-applies the promo code
                      package_id:
                        type: string
                        nullable: true
                        description: >-
                          ID of the associated package (null or empty string
                          when no package is assigned)
                      package_size:
                        type: string
                        description: Size of the package
                      destination:
                        type: string
                        nullable: true
                        description: Destination name (null when no country is resolved)
                      iso3:
                        type: string
                        description: ISO3 country code
                      package_type:
                        type: string
                        description: Type of package
                      package_duration:
                        type: integer
                        description: Duration in days
        '400':
          description: Validation or server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized - Missing or invalid authentication or RBAC permission
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: >-
            Promo code not found or partner trying to access another
            organization's promo code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /promo-codes/claim/{uuid}:
    post:
      tags:
        - PromoCode
      summary: Claim a promo code using UUID
      description: >
        Public endpoint (no authentication required) to claim a promo code by
        providing an email address.

        The user receives the promo code via email immediately after claiming.
      security: []
      parameters:
        - name: uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The unique identifier from the claim URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  description: Email address to receive the promo code
                  example: user@example.com
      responses:
        '200':
          description: Promo code claimed successfully and email sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Promo code claimed and email sent successfully
                  data:
                    type: object
                    example: {}
        '400':
          description: Bad request (invalid email, already claimed, or expired)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '404':
          description: UUID or booking not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /esims/topUp:
    post:
      tags:
        - eSIM
      summary: Top-up an eSIM
      description: Top-up an eSIM using a package ID and ICCID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - packageId
                - iccid
                - isAndroid
              properties:
                packageId:
                  type: string
                  description: The ID of the package to be used for the top-up.
                iccid:
                  type: string
                  description: The ICCID of the eSIM to top-up.
                isAndroid:
                  type: boolean
                  description: Whether the device is Android or not.
      responses:
        '200':
          description: eSIM top-up successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: eSIM top-up successful
                  data:
                    type: object
                    description: Information related to the eSIM top-up
                    properties:
                      packageId:
                        type: string
                        description: The ID of the package that was loaded
                      iccid:
                        type: string
                        description: The ICCID of the eSIM
                      status:
                        type: string
                        description: >-
                          Current status of the loaded package (e.g.
                          "NOT_ACTIVE", "ACTIVE", "TERMINATED")
                      dataUsageRemainingInBytes:
                        type: number
                        description: Remaining data usage in bytes
                      dateActivated:
                        type: string
                        format: date-time
                        nullable: true
                        description: >-
                          When the package was activated, or null if not yet
                          activated
                      dateTerminated:
                        type: string
                        format: date-time
                        nullable: true
                        description: >-
                          When the package was terminated, or null if still
                          active
                      dateExpiry:
                        type: string
                        format: date-time
                        nullable: true
                        description: When the package expires, or null if no expiry set
                      packageTypeId:
                        type: string
                        description: The ID of the package template used
                      timeAllowanceInSeconds:
                        type: integer
                        nullable: true
                        description: >-
                          Time allowance for the package in seconds, or null if
                          not applicable
        '400':
          description: Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: eSIM or package not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /esims/data-usage/{iccid}:
    get:
      tags:
        - eSIM
      summary: Get eSIM data usage
      description: Retrieves data usage information for a specific eSIM
      parameters:
        - name: iccid
          in: path
          required: true
          schema:
            type: string
          description: The ICCID of the eSIM to retrieve data usage for
      responses:
        '200':
          description: Successful data usage retrieval
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: eSIM data usage retrieved successfully
                  data:
                    type: object
                    properties:
                      total_data:
                        type: number
                        nullable: true
                        description: Total data allocation in bytes
                        example: 1073741824
                      data_left:
                        type: number
                        nullable: true
                        description: Remaining data in bytes
                        example: 536870912
                      data_used:
                        oneOf:
                          - type: number
                          - type: boolean
                        description: Data used in bytes or false if cannot be calculated
                        example: 536870912
                      last_updated:
                        type: string
                        format: date-time
                        description: Timestamp of the last data usage update
                        example: '2023-06-15T10:30:00Z'
                      status:
                        type: string
                        nullable: true
                        description: Current status of the eSIM
                        example: ACTIVE
        '400':
          description: Invalid ICCID provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: eSIM not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /redirect-tokens/create:
    post:
      tags:
        - WebView
      summary: Create a redirect token
      description: >
        Generates a short-lived redirect token that the partner app uses to open
        the Hubby WebView. The token is valid for 5 minutes and is single-use.


        **When to call:** Every time the traveler opens the eSIM section in your
        app. Do not cache the returned token — generate a fresh token on every
        open, on app resume, and if the 5-minute window expires.


        **Authentication:** HMAC-SHA256 (same as all partner endpoints).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - external_user_id
              properties:
                external_user_id:
                  type: string
                  description: >
                    The partner's own user identifier — your internal ID for
                    this traveler. This is **not** Hubby's `user_id` from the
                    booking API.
                  example: partner_user_456
                redirect_token:
                  type: string
                  format: uuid
                  description: >
                    Optional. If you pre-generate the token, pass it here.
                    Otherwise Hubby generates one.
                  example: b3864ab6-c13b-42b5-9f69-935eb9c34aba
      responses:
        '200':
          description: Redirect token created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      redirect_token:
                        type: string
                        format: uuid
                        description: >-
                          Short-lived token that the partner app uses to open
                          the Hubby WebView. Valid for 5 minutes and single-use.
                        example: 1b80dfe9-0202-4151-a26f-ceac52ca3b34
                      expires_in:
                        type: integer
                        description: Token lifetime in seconds
                        example: 300
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /webapp/auth/exchange:
    post:
      tags:
        - WebView
      summary: Exchange redirect token for session JWT
      description: >
        Exchanges a redirect token for a short-lived session JWT. The JWT is
        used as a Bearer token for all subsequent WebView API calls.


        **Called automatically by the Hubby WebView** — partners do not call
        this endpoint directly. Documented for transparency and to aid debugging
        of WebView network traffic.


        **Authentication:** None (the redirect token itself is the credential).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - redirect_token
                - external_user_id
              properties:
                redirect_token:
                  type: string
                  format: uuid
                  description: The redirect token returned by `/redirect-tokens/create`.
                  example: 1b80dfe9-0202-4151-a26f-ceac52ca3b34
                external_user_id:
                  type: string
                  description: >-
                    The partner's own user identifier. Must match the
                    `external_user_id` used when creating the redirect token.
                  example: '123'
      responses:
        '200':
          description: Token exchanged successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      access_token:
                        type: string
                        description: >-
                          Session JWT for WebView API calls. Use as
                          Authorization: Bearer {token}.
                        example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                      token_type:
                        type: string
                        example: Bearer
                      expires_in:
                        type: integer
                        description: Token lifetime in seconds
                        example: 1209600
        '400':
          description: Validation error (e.g., missing fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Invalid or expired redirect token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /webapp/me/dashboard:
    get:
      tags:
        - WebView
      summary: Get user dashboard
      description: >
        Returns the authenticated traveler's dashboard data: active packages,
        data usage, unclaimed packages, and available actions.


        **Called automatically by the Hubby WebView** — partners do not call
        this endpoint directly. Documented for transparency and to aid debugging
        of WebView network traffic.


        **Authentication:** Bearer JWT from `/webapp/auth/exchange`.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Dashboard data
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    description: >-
                      Dashboard payload including active packages, usage, and
                      unclaimed packages. Structure may evolve — the WebView
                      renders this automatically.
                    properties:
                      unclaimed_packages:
                        type: array
                        description: Packages the traveler has not yet claimed
                        items:
                          type: object
                      active_packages:
                        type: array
                        description: Currently active packages with usage data
                        items:
                          type: object
                      expired_packages:
                        type: array
                        description: Previously active packages that have expired
                        items:
                          type: object
        '401':
          description: Invalid or expired session JWT
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /webapp/refresh-esim:
    post:
      tags:
        - WebView
      summary: Refresh a user's universal eSIM
      description: >
        Assigns a fresh universal eSIM to a user identified by
        `external_user_id`. If the user already has a universal eSIM, it is
        replaced with a new one. If the user does not yet have one, a new eSIM
        is provisioned and assigned.


        Use this endpoint to pre-provision an eSIM for a traveler before they
        open the WebView, or to replace a problematic eSIM with a fresh one.


        **Authentication:** Bearer JWT from `/webapp/auth/exchange`.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - external_user_id
              properties:
                external_user_id:
                  type: string
                  description: >-
                    The partner's own user identifier. Must match the
                    `external_user_id` used during authentication.
                  example: partner_user_456
      responses:
        '200':
          description: Universal eSIM assigned successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      iccid:
                        type: string
                        description: ICCID of the newly assigned universal eSIM
                        example: '8901234567890123456'
                      qr:
                        type: string
                        description: >-
                          LPA string for eSIM installation (use for QR code
                          generation or programmatic install)
                        example: LPA:1$smdp.example.com$ACTIVATION-CODE
                      status:
                        type: string
                        description: >-
                          Initial eSIM profile status (will be `RELEASED` for a
                          freshly assigned eSIM)
                        example: RELEASED
        '400':
          description: Validation error (e.g., missing `external_user_id`)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Invalid or expired session JWT
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /native/user-dashboard:
    get:
      tags:
        - Native eSIM Integration
      summary: Get user dashboard
      description: >
        Returns everything needed to render any screen in a native eSIM
        integration: user info, package queues, current eSIM state, active
        packages, and partner details.


        This is the central endpoint for the Native eSIM Integration. One call
        gives you the traveler's full state.


        **When to call:**

        - Every time the traveler opens the eSIM section

        - After a top-up purchase (to confirm the new package appeared)

        - After redeeming a package queue

        - Periodically for data meter refresh (or rely on webhooks)


        **Authentication:** HMAC-SHA256 (same as all partner endpoints).
      parameters:
        - name: external_user_id
          in: query
          required: true
          schema:
            type: string
          description: The partner's own user identifier
          example: user_abc123
      responses:
        '200':
          description: User dashboard retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      user:
                        type: object
                        description: Core user information
                        properties:
                          id:
                            type: string
                            description: Internal Hubby user ID
                            example: firestore-user-id
                          external_user_id:
                            type: string
                            description: The partner's own user identifier
                            example: user_abc123
                          name:
                            type: string
                            nullable: true
                            description: User's display name (null if not set)
                            example: null
                          email:
                            type: string
                            description: User's email address
                            example: user@example.com
                          locale:
                            type: string
                            nullable: true
                            description: User's preferred locale (e.g., "en", "de")
                            example: null
                          referral:
                            type: string
                            description: User's referral code
                            example: ABC123XYZ
                          currency:
                            type: string
                            nullable: true
                            description: User's preferred currency (null if not set)
                            example: null
                          has_universal_esim:
                            type: boolean
                            description: Whether the user has a universal eSIM assigned
                            example: true
                          current_universal_esim:
                            type: string
                            nullable: true
                            description: >-
                              ICCID of the user's current universal eSIM (null
                              if none)
                            example: '8901234567890123456'
                      package_queues:
                        type: array
                        description: >-
                          Pending package queues for this user. Each entry
                          represents a booked package that can be redeemed.
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              description: >-
                                Package queue identifier — use this as
                                `package_queue_id` when calling
                                `/native/redeem-package`
                              example: pq_xyz789
                            uuid:
                              type: string
                              format: uuid
                              description: Unique identifier for this package queue entry
                              example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                            type:
                              type: string
                              description: How this package queue was created
                              example: booking
                            package_specification:
                              type: object
                              description: The package specification for this queue entry
                              properties:
                                iso3:
                                  type: string
                                  description: >-
                                    ISO 3166-1 alpha-3 country code for the
                                    destination
                                  example: FRA
                                bytes:
                                  type: integer
                                  description: Total data allowance in bytes
                                  example: 1073741824
                                duration_days:
                                  type: integer
                                  description: Package validity in days
                                  example: 30
                            created_at:
                              type: string
                              format: date-time
                              description: When this package queue was created
                              example: '2026-03-20T10:00:00.000Z'
                            redeemed_at:
                              type: string
                              format: date-time
                              nullable: true
                              description: >-
                                When this package queue was redeemed (null if
                                not yet redeemed)
                              example: null
                      current_esim:
                        type: object
                        nullable: true
                        description: >-
                          The user's current universal eSIM (null if no eSIM
                          assigned)
                        properties:
                          provider:
                            type: string
                            description: eSIM provider
                            example: esim-provider
                          name:
                            type: string
                            description: eSIM display name
                            example: Hubby Universal ESIM
                          qr:
                            type: string
                            description: >-
                              LPA string for eSIM installation (use for QR code
                              generation or programmatic install)
                            example: LPA:1$smdp.example.com$ACTIVATION-CODE
                          iccid:
                            type: string
                            description: ICCID of the eSIM
                            example: '8901234567890123456'
                          destination_iso3:
                            type: string
                            description: >-
                              ISO 3166-1 alpha-3 country code of the current
                              destination
                            example: FRA
                          total_data:
                            type: integer
                            description: Total data across all active packages in bytes
                            example: 1073741824
                          data_left:
                            type: integer
                            description: Remaining data in bytes
                            example: 524288000
                          data_used:
                            type: boolean
                            description: Whether any data has been consumed
                            example: false
                          status:
                            type: string
                            description: >
                              eSIM profile status:

                              - `RELEASED` — profile downloaded and ready

                              - `INSTALLED` — profile installed on device

                              - `ENABLED` — profile active and connected

                              - `Local Enable` — profile locally enabled on
                              device

                              - `Local Disable` — profile locally disabled on
                              device
                            example: RELEASED
                          time_assigned:
                            type: string
                            format: date-time
                            description: When the eSIM was assigned to this user
                            example: '2026-03-21T12:00:00.000Z'
                      current_esim_packages:
                        type: array
                        description: Packages currently on the user's eSIM
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                              description: Unique identifier for this package
                              example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                            provider:
                              type: string
                              description: eSIM connectivity provider
                              example: esim-provider
                            status:
                              type: string
                              description: |
                                Package status:
                                - `ACTIVE` — currently usable
                                - `NOT_ACTIVE` — assigned but not yet activated
                                - `DEPLETED` — data exhausted or expired
                              example: ACTIVE
                            total_bytes:
                              type: integer
                              description: Total data allowance in bytes
                              example: 1073741824
                            bytes_remaining:
                              type: integer
                              description: Remaining data in bytes
                              example: 1073741824
                            template:
                              type: string
                              description: Package template identifier
                              example: DATA_LIMITED_1GB_365_DAYS
                            destination_bundle:
                              type: string
                              description: Destination bundle identifier
                              example: DATA_LIMITED_1GB_365_DAYS
                            destination_iso3:
                              type: string
                              description: ISO 3166-1 alpha-3 destination country code
                              example: FRA
                            type:
                              type: string
                              description: Package type (e.g., "data-limited", "unlimited")
                              example: data-limited
                            allowance_seconds:
                              type: integer
                              description: Package validity period in seconds
                              example: 31536000
                            assigned_at:
                              type: string
                              format: date-time
                              description: When the package was assigned to the eSIM
                              example: '2026-03-21T12:00:00.000Z'
                            date_activated:
                              type: string
                              format: date-time
                              nullable: true
                              description: >-
                                When the package was activated (null if not yet
                                activated)
                              example: null
                      partner:
                        type: object
                        description: Partner information
                        properties:
                          id:
                            type: string
                            description: Partner identifier
                            example: partner-id
                          name:
                            type: string
                            description: Partner display name
                            example: Partner Name
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /native/instructions:
    post:
      tags:
        - Native eSIM Integration
      summary: Get installation instructions
      description: >
        Returns AI-generated, device-specific eSIM installation instructions
        tailored to the traveler's exact device model, OS version, locale, and
        current eSIM state.


        The service resolves the user's eSIM status, activation string, and
        partner visual identity automatically from the `external_user_id`, then
        generates branded, device-aware instructions.


        You do not need to hard-code or maintain installation content in your
        app — Hubby keeps instructions current for every device and OS version.


        **Authentication:** HMAC-SHA256 (same as all partner endpoints).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - external_user_id
                - deviceName
                - os
                - osVersion
              properties:
                format:
                  type: string
                  description: Response format for the instructions
                  example: html
                external_user_id:
                  type: string
                  description: The partner's own user identifier
                  example: user_abc123
                deviceName:
                  type: string
                  description: Full device name
                  example: Google Pixel 10
                deviceType:
                  type: string
                  description: Device type (phone, tablet, etc.)
                  example: phone
                os:
                  type: string
                  description: Operating system
                  example: android
                osVersion:
                  type: string
                  description: OS version
                  example: '15'
                locale:
                  type: string
                  description: Locale for the instructions (e.g., "en", "es", "mk-MK")
                  example: mk-MK
      responses:
        '200':
          description: >-
            Installation instructions generated successfully. The response shape
            is dynamic and depends on the requested format.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    description: >-
                      AI-generated installation instructions. Shape varies by
                      format.
        '400':
          description: Missing required fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /native/device-compatibility:
    post:
      tags:
        - Native eSIM Integration
      summary: Check device eSIM compatibility
      description: >
        Checks whether a specific device supports eSIM. Pass the device brand,
        model, OS, and OS version to get a compatibility result.


        **Authentication:** HMAC-SHA256 (same as all partner endpoints).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - phone_brand
                - phone_model
                - phone_os
                - os_version
              properties:
                phone_brand:
                  type: string
                  description: Device manufacturer
                  example: Google
                phone_model:
                  type: string
                  description: Device model name
                  example: Pixel 10
                phone_os:
                  type: string
                  description: Operating system
                  example: android
                os_version:
                  type: string
                  description: OS version
                  example: '15'
      responses:
        '200':
          description: Compatibility check result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      phone_brand:
                        type: string
                        example: Google
                      phone_model:
                        type: string
                        example: Pixel 10
                      phone_os:
                        type: string
                        example: android
                      os_version:
                        type: string
                        example: '15'
                      is_compatible:
                        type: boolean
                        description: Whether the device supports eSIM
                        example: true
        '400':
          description: Missing required fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
    get:
      tags:
        - Native eSIM Integration
      summary: List device compatibility
      description: >
        Returns a list of all known devices and their eSIM compatibility status.
        Use this to build a device compatibility page or to pre-filter device
        selection in your app.


        **Authentication:** HMAC-SHA256 (same as all partner endpoints).
      responses:
        '200':
          description: Device compatibility list
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      devices:
                        type: array
                        description: >-
                          List of devices with compatibility info. May be empty
                          while the database is being populated.
                        items:
                          type: object
                          properties:
                            phone_brand:
                              type: string
                              description: Device manufacturer
                              example: Apple
                            phone_model:
                              type: string
                              description: Device model name
                              example: iPhone 15 Pro
                            phone_os:
                              type: string
                              description: Operating system
                              example: iOS
                            min_os_version:
                              type: string
                              description: Minimum OS version required for eSIM support
                              example: '16.0'
                            is_compatible:
                              type: boolean
                              description: Whether the device supports eSIM
                              example: true
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
  /native/redeem-package:
    post:
      tags:
        - Native eSIM Integration
      summary: Redeem a package queue
      description: >
        Redeems a package queue entry, provisioning the package onto the
        traveler's universal eSIM. The package queue entry is consumed and the
        eSIM is updated with the new package.


        Use the `package_queue_id` from the user dashboard response (`GET
        /native/user-dashboard`) to identify which package queue to redeem.


        **Authentication:** HMAC-SHA256 (same as all partner endpoints).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - external_user_id
                - package_queue_id
              properties:
                external_user_id:
                  type: string
                  description: The partner's own user identifier
                  example: user_abc123
                package_queue_id:
                  type: string
                  description: >-
                    The package queue ID to redeem (from the `id` field in the
                    `package_queues` array of the user dashboard response)
                  example: pq_xyz789
      responses:
        '200':
          description: Package queue redeemed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Package queue redeemed successfully
                  data:
                    type: object
                    properties:
                      packageQueue:
                        type: object
                        description: The redeemed package queue entry
                        properties:
                          uuid:
                            type: string
                            format: uuid
                            description: Unique identifier of the package queue entry
                            example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                          type:
                            type: string
                            description: How this package queue was created
                            example: booking
                          redeemed_at:
                            type: string
                            format: date-time
                            nullable: true
                            description: >-
                              When this package queue was redeemed (null if
                              redemption is still processing)
                            example: null
                          package_specification:
                            type: object
                            properties:
                              iso3:
                                type: string
                                description: ISO 3166-1 alpha-3 destination country code
                                example: FRA
                              bytes:
                                type: integer
                                description: Total data allowance in bytes
                                example: 1073741824
                              duration_days:
                                type: integer
                                description: Package validity in days
                                example: 30
                      current_esim:
                        type: object
                        description: Updated eSIM state after redemption
                        properties:
                          iccid:
                            type: string
                            description: ICCID of the eSIM
                            example: '8901234567890123456'
                          qr:
                            type: string
                            description: LPA string for eSIM installation
                            example: LPA:1$smdp.example.com$ACTIVATION-CODE
                          name:
                            type: string
                            description: eSIM display name
                            example: Hubby Universal ESIM
                          status:
                            type: string
                            description: eSIM profile status
                            example: RELEASED
                          data_left:
                            type: integer
                            description: Remaining data in bytes
                            example: 1073741824
                          total_data:
                            type: integer
                            description: Total data in bytes
                            example: 1073741824
                          last_updated:
                            type: string
                            format: date-time
                            description: When eSIM data was last refreshed
                            example: '2026-03-23T12:00:00.000Z'
                          time_assigned:
                            type: string
                            format: date-time
                            description: When the eSIM was assigned
                            example: '2026-03-23T12:00:00.000Z'
                          esim_type:
                            type: string
                            description: Type of eSIM
                            example: classic
                          provider:
                            type: string
                            description: eSIM provider
                            example: esim-provider
                      current_esim_packages:
                        type: array
                        description: Updated list of packages on the eSIM after redemption
                        items:
                          type: object
                          properties:
                            allowance_seconds:
                              type: integer
                              description: Package validity in seconds
                              example: 2592000
                            allowance_hours:
                              type: integer
                              description: Package validity in hours
                              example: 720
                            allowance_days:
                              type: integer
                              description: Package validity in days
                              example: 30
                            assigned_at:
                              type: string
                              format: date-time
                              description: When the package was assigned to the eSIM
                              example: '2026-03-23T12:00:00.000Z'
                            date_activated:
                              type: string
                              format: date-time
                              nullable: true
                              description: >-
                                When the package was activated (null if not yet
                                activated)
                              example: null
                            status:
                              type: string
                              description: Package status
                              example: ACTIVE
                            total_bytes:
                              type: integer
                              description: Total data allowance in bytes
                              example: 1073741824
                            type:
                              type: string
                              description: Package type
                              example: data
        '400':
          description: Package queue cannot be redeemed (e.g., already redeemed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorResponse'
        '404':
          description: User or package queue not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerErrorResponse'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Session JWT obtained from `/webapp/auth/exchange`. Used only by WebView
        endpoints.
  schemas:
    BookingRequest:
      type: object
      required:
        - departure_date
        - package_specifications
      properties:
        title:
          type: string
        first_name:
          type: string
          minLength: 1
          maxLength: 100
        last_name:
          type: string
          minLength: 1
          maxLength: 100
        full_name:
          type: string
          minLength: 1
          maxLength: 200
        pax:
          type: integer
          minimum: 1
        email:
          type: string
          format: email
          description: Required if `booking_id` is not provided.
        phone:
          type: string
          pattern: ^\+\d{1,3}\d{1,14}$
        booking_id:
          type: string
          minLength: 3
          description: Required if `email` is not provided.
        external_id:
          nullable: true
          type: string
          description: Optional external identifier for the booking
        return_date:
          type: string
          format: date-time
          description: ISO 8601 return date. Must be after `departure_date`.
        departure_date:
          type: string
          format: date-time
          description: 'ISO 8601 departure date. Example: ''2026-01-03''.'
        gender:
          type: string
          enum:
            - M
            - F
            - O
        locale:
          type: string
          minLength: 2
          maxLength: 5
          description: Locale or language code (e.g., "en", "nl").
        communication_options:
          type: object
          properties:
            should_send_message:
              type: boolean
            channels:
              type: array
              items:
                type: string
                enum:
                  - EMAIL
                  - PUSH
                  - SMS
                  - WHATSAPP
        custom_branding:
          type: string
          nullable: true
          description: >
            Sets a specific visual style for the app experience. The brand
            identifier must correspond to a brand configuration that exists in
            the Hubby platform. Contact support to set up custom branding for
            your organization.
        departure_location:
          type: string
          nullable: true
          description: >
            The departure location for the booking (e.g., airport code, city
            name, or address). Used to provide context about where the traveler
            is departing from.
        package_specifications:
          type: array
          minItems: 1
          description: >
            A list of package specifications. At least one entry is required.

            Each entry is resolved either by `package_id` (direct reference) or
            by other properties (destination, size, package_type, etc.); when
            `package_id` is present, other fields in that entry are ignored for
            resolution.
          items:
            type: object
            properties:
              destination:
                oneOf:
                  - type: string
                    pattern: ^[A-Z]{2,3}$
                    description: |
                      Single destination code (ISO 2-3 uppercase letters).
                      Example: 'US', 'NL'.
                  - type: array
                    items:
                      type: string
                      pattern: ^[A-Z]{2,3}$
                    minItems: 1
                    description: >
                      Array of destination codes for multi-destination bookings.

                      The system will optimize ESIM selection to cover all
                      destinations with the fewest ESIMs.

                      Example: ['US', 'CA', 'MX'] for North American trip.
                description: >
                  Destination code(s) for the booking. Can be a single
                  destination or an array of destinations for multi-destination
                  trips with ESIM optimization.
              size:
                type: string
                pattern: ^[0-9]\d*(\.\d+)?(MB|GB)$
                description: >-
                  Package size (e.g., '500MB', '1GB', '3GB', '5GB', '10GB',
                  '20GB')
              package_id:
                type: string
                description: >
                  Direct package reference. When set, this specification is
                  resolved only by this identifier;

                  other fields in the same specification are not used for
                  resolution and are effectively ignored.
              package_type:
                type: string
                enum:
                  - starter
                  - data-limited
                  - unlimited
                  - time-limited
                default: data-limited
                description: >
                  Type of package to create. 

                  - `starter`: Small data allowance for short duration, ideal
                  for trials (default: 2 days)

                  - `data-limited`: Traditional packages with specific data
                  allowance (default: 365 days)

                  - `unlimited`: Unrestricted data usage with fair use policy

                  - `time-limited`: Fixed data allowance with time constraint
                  for full-speed access
              package_duration:
                type: integer
                minimum: 1
                default: 365
                description: |
                  Duration of the package in days. 
                  - Defaults to 365 days for data-limited packages
                  - Defaults to 2 days for starter and time-limited packages
      description: >
        Booking request object. Either `email` or `booking_id` is required. The
        `departure_date` and `package_specifications` are mandatory.
    BookingResponse:
      type: object
      properties:
        external_id:
          type: string
          nullable: true
          description: External identifier for the booking
        id:
          type: string
          description: Unique identifier of the booking
        title:
          type: string
          description: Title
        first_name:
          type: string
          description: First name
        last_name:
          type: string
          description: Last name
        full_name:
          type: string
          description: Full name
        pax:
          type: integer
          description: Number of passengers
        email:
          type: string
          format: email
          description: Email address of the booking contact
        phone:
          type: string
          nullable: true
          description: Phone number of the booking contact
        booking_id:
          type: string
          nullable: true
          description: ID of the booking
        return_date:
          type: string
          format: date-time
          description: Return date in ISO format
        departure_date:
          type: string
          format: date-time
          description: Departure date in ISO format
        gender:
          type: string
          enum:
            - M
            - F
            - O
          description: Gender
        locale:
          type: string
          description: Locale of the booking
        promo_codes:
          type: array
          items:
            type: object
            properties:
              promo_code:
                type: string
                description: Promo code applied to the booking
              uuid:
                type: string
                format: uuid
                nullable: true
                description: >-
                  Unique identifier for the promo code claim link (null for
                  legacy codes)
              claim_url:
                type: string
                format: uri
                nullable: true
                description: >-
                  Direct link for users to claim the promo code with their email
                  (only included when booking has no email)
              deep_link_url:
                type: string
                format: uri
                nullable: true
                description: >-
                  Mobile deep link that opens the Hubby app and auto-applies the
                  promo code
              package_id:
                type: string
                description: ID of the package associated with the promo code
              package_size:
                type: string
                description: Size of the package associated with the promo code
              destination:
                type: string
                description: Destination associated with the promo code
              iso3:
                type: string
                nullable: true
                description: ISO3 country code for the destination
              package_type:
                type: string
                nullable: true
                description: >-
                  Type of package (starter, data-limited, unlimited,
                  time-limited)
              package_duration:
                type: integer
                nullable: true
                description: Duration of the package in days
        created_at:
          type: string
          format: date-time
          description: Creation timestamp of the booking
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp of the booking
        created_by:
          type: string
          description: Identifier of the entity that created the booking
        updated_by:
          type: string
          description: Identifier of the entity that last updated the booking
        custom_branding:
          type: string
          nullable: true
          description: >
            Sets a specific visual style for the app experience. The brand
            identifier must correspond to a brand configuration that exists in
            the Hubby platform.
        departure_location:
          type: string
          nullable: true
          description: >
            The departure location for the booking (e.g., airport code, city
            name, or address). Used to provide context about where the traveler
            is departing from.
    UpdatePackageSpecForPromoCodesResponse:
      allOf:
        - $ref: '#/components/schemas/BookingResponse'
        - type: object
          required:
            - updated_count
          properties:
            updated_count:
              type: integer
              description: Number of promo codes that were updated.
            skipped_count:
              type: integer
              description: >-
                Present when using the bulk update endpoint and some promo codes
                were already redeemed and therefore skipped.
    PackageSpecification:
      type: object
      properties:
        package_id:
          type: string
          description: >
            ID of an existing package. At least one of `package_id` or
            `package_type` must be provided.


            **When `package_id` is provided without `package_type`:** The
            package's properties (`package_type`, `size`, `package_duration`,
            `destination`) are resolved automatically from the referenced
            package.


            **When `package_id` is provided with `package_type`:** The explicit
            fields from the request body are used instead of the package's own
            properties.
        package_type:
          type: string
          enum:
            - starter
            - data-limited
            - unlimited
            - time-limited
          description: >
            Type of package. At least one of `package_id` or `package_type` must
            be provided.

            - `starter`: Small data allowance for short duration, ideal for
            trials

            - `data-limited`: Traditional packages with specific data allowance

            - `unlimited`: Unrestricted data usage with fair use policy

            - `time-limited`: Fixed data allowance with time constraint for
            full-speed access
        size:
          type: string
          pattern: ^[0-9]\d*(\.\d+)?(MB|GB)$
          description: >
            Package size (e.g. `"1GB"`, `"5GB"`).

            Required when `package_type` is `data-limited`, `time-limited`, or
            `starter`. Not required for `unlimited`.
        package_duration:
          type: integer
          minimum: 1
          description: >
            Duration of the package in days.

            Required when `package_type` is `time-limited`, `unlimited`, or
            `starter`. Not required for `data-limited`.
        destination:
          oneOf:
            - type: string
              pattern: ^[A-Z]{2,3}$
              description: Single destination code (ISO3 or IATA code)
              example: ESP
            - type: array
              items:
                type: string
                pattern: ^[A-Z]{2,3}$
              minItems: 1
              description: Array of destination codes for multi-destination
              example:
                - USA
                - MKD
                - TUR
          description: >-
            Country ISO3 code or IATA code, or an array for multi-destination.
            Optional — omit for destination-agnostic promo codes.
      description: >
        Package specification for updating promo codes. At least one of
        `package_id` or `package_type` must be provided.


        **When only `package_id` is sent:** The package's properties
        (`package_type`, `size`, `package_duration`, `destination`) are resolved
        automatically from the referenced package.


        **When `package_id` is sent with `package_type`:** The explicit fields
        from the request body are used instead of the package's own properties.


        **When only `package_type` is sent:** The conditional field requirements
        apply based on the type.


        **Package type requirements:**


        - **`data-limited`** — `size`: required, `package_duration`: not
        required

        - **`time-limited`** — `size`: required, `package_duration`: required

        - **`unlimited`** — `size`: not required, `package_duration`: required

        - **`starter`** — `size`: required, `package_duration`: required
    DestinationResponse:
      type: object
      properties:
        id:
          type: string
          description: >-
            Unique identifier of the destination. ISO3 code for countries (e.g.
            "NLD"), or a region code (e.g. "XA" for Asia).
          example: BES
        type:
          type: string
          enum:
            - country
            - region
          description: >-
            Whether this destination is a single country or a multi-country
            region.
          example: country
        tier:
          type: integer
          nullable: true
          description: Pricing tier level for this destination. Null for regions.
          example: 2
        iso3s:
          type: array
          items:
            type: string
          description: >-
            List of ISO3 country codes covered by this destination. For
            countries, contains a single code. For regions, contains all
            included countries.
          example:
            - BES
        name:
          type: string
          description: Display name of the destination in English.
          example: Bonaire
        i18n_name:
          type: object
          additionalProperties:
            type: string
          description: Localized destination names keyed by locale code.
          example:
            en: Bonaire
            nl: Bonaire
            de: Bonaire
            fr: Bonaire
            es: Bonaire
            zh: 荷兰加勒比区
            ko: 보네르
        is_active:
          type: boolean
          description: Whether this destination is currently active and available.
          example: true
        sort_order:
          type: integer
          description: Sort priority for display ordering.
          example: 0
        created_at:
          type: string
          format: date-time
          description: Timestamp when the destination was created.
          example: '2026-01-16T11:57:02.312Z'
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the destination was last updated.
          example: '2026-01-16T11:57:02.312Z'
        created_by:
          type: string
          description: Identifier of the process or user that created this destination.
          example: migrateCountriesToDestinations.py
        updated_by:
          type: string
          description: >-
            Identifier of the process or user that last updated this
            destination.
          example: migrateCountriesToDestinations.py
    DestinationBundleResponse:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier of the bundle.
          example: DATA_LIMITED_10GB_365_DAYS_XA
        parent_document_id:
          type: string
          description: The destination ID this bundle belongs to.
          example: XA
        type:
          type: string
          description: The type of data package.
          example: data-limited
        label:
          type: string
          description: Human-readable label for the bundle size.
          example: 10GB
        provider:
          type: string
          description: The eSIM connectivity provider for this bundle.
          example: esim-provider
        duration_in_days:
          type: integer
          description: Bundle validity period in days.
          example: 365
        duration_in_seconds:
          type: integer
          description: Bundle validity period in seconds.
          example: 31536000
        size_in_bytes:
          type: integer
          description: Data allowance in bytes.
          example: 10000000000
    DestinationDetailResponse:
      allOf:
        - $ref: '#/components/schemas/DestinationResponse'
        - type: object
          properties:
            bundles:
              type: array
              items:
                $ref: '#/components/schemas/DestinationBundleResponse'
              description: List of available data bundles (packages) for this destination.
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          description: Optional success message
        data:
          type: object
          description: Main data payload
    NotFoundErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: integer
              description: HTTP status code of the error
              example: 404
            message:
              type: string
              description: Error message detailing the issue
            details:
              type: object
              nullable: true
              description: Optional additional error details
    ValidationErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: integer
              description: HTTP status code of the error
              example: 400
            message:
              type: string
              description: Error message detailing the issue
            details:
              type: object
              nullable: true
              description: Optional additional error details
    UnauthorizedErrorResponse:
      type: object
      description: >
        Authentication failed with specific messages:

        - "Missing required HMAC authentication headers" - When x-api-key,
        x-timestamp, or x-signature are missing

        - "Request timestamp expired" - When timestamp is more than 400 minutes
        old or in future

        - "Invalid signature" - When HMAC signature verification fails


        HMAC Signature Generation:

        1. Create message string by concatenating:
           `${timestamp}${method}${path}?${queryString}`
           Example: "1678901234GET/bookings?perPage=10"
        2. Generate HMAC-SHA256 using your API secret

        3. Convert to hex string


        Notes:

        - Timestamp must be within 400 minutes of current time

        - API keys expire after 1 year

        - Query parameters must be included in signature if present
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: integer
              description: HTTP status code of the error
              example: 401
            message:
              type: string
              description: Error message detailing the issue
            details:
              type: object
              nullable: true
              description: Optional additional error details
    ServerErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: integer
              description: HTTP status code of the error
              example: 500
            message:
              type: string
              description: Error message detailing the issue
            details:
              type: object
              nullable: true
              description: Optional additional error details
    PaginationMeta:
      type: object
      properties:
        perPage:
          type: integer
          description: Number of items per page (minimum 1)
        hasNextPage:
          type: boolean
          description: Indicates if there are more items after this page
        hasPrevPage:
          type: boolean
          description: Indicates if there are items before this page
        nextCursor:
          type: string
          nullable: true
          description: Cursor to get the next page of results
        prevCursor:
          type: string
          nullable: true
          description: Cursor to get the previous page of results
    WebhookEnvelope:
      type: object
      required:
        - event
        - timestamp
        - data
      properties:
        event:
          type: string
          description: Event type identifier
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 UTC timestamp of when the event occurred
        data:
          type: object
          description: Event-specific payload
    PackageUsageWebhookData:
      type: object
      description: >
        Per-package usage event data. These events are emitted per package, not
        per eSIM.

        For data-limited packages, thresholds are based on data consumption.

        For time-limited packages, thresholds are based on duration elapsed.
      properties:
        external_user_id:
          type: string
          description: Partner's own user identifier
          example: partner_user_456
        booking_id:
          type: string
          description: Booking ID
          example: booking_abc
        package_id:
          type: string
          description: The specific package that triggered the event
          example: pkg_xyz
        destination:
          type: string
          description: ISO country code
          example: GR
        size:
          type: string
          description: Package size (data-limited packages only)
          example: 1GB
        package_type:
          type: string
          enum:
            - data-limited
            - time-limited
            - unlimited
            - starter
          description: Type of package
          example: data-limited
        used_bytes:
          type: integer
          description: Bytes consumed so far (data-limited packages)
          example: 858993459
        remaining_bytes:
          type: integer
          description: Bytes remaining (data-limited packages)
          example: 214748365
        duration_days:
          type: integer
          description: Total package validity in days (time-limited packages)
          example: 30
        elapsed_days:
          type: integer
          description: Days elapsed since activation (time-limited packages)
          example: 24
        remaining_days:
          type: integer
          description: Days remaining (time-limited packages)
          example: 6
        usage_percent:
          type: integer
          description: >-
            Usage percentage that triggered this event (20, 50, or 80).
            Represents data consumed (data-limited) or duration elapsed
            (time-limited).
          example: 80
    EsimStatusWebhookData:
      type: object
      properties:
        external_user_id:
          type: string
          description: Partner's own user identifier
          example: partner_user_456
        booking_id:
          type: string
          description: Booking ID
          example: booking_abc
        iccid:
          type: string
          description: ICCID of the eSIM
          example: '8901234567890123456'
    PackageActivatedWebhookData:
      type: object
      properties:
        external_user_id:
          type: string
          description: Partner's own user identifier
          example: partner_user_456
        booking_id:
          type: string
          description: Booking ID
          example: booking_abc
        package_id:
          type: string
          description: Package ID
          example: pkg_xyz
        destination:
          type: string
          description: ISO country code
          example: GR
        size:
          type: string
          description: Package size
          example: 1GB
        activated_at:
          type: string
          format: date-time
          description: When the package was activated
          example: '2026-07-15T16:00:00Z'
        expires_at:
          type: string
          format: date-time
          description: When the package expires
          example: '2027-07-15T16:00:00Z'
    BookingWithinCutoffWebhookData:
      type: object
      properties:
        external_user_id:
          type: string
          description: Partner's own user identifier
          example: partner_user_456
        booking_id:
          type: string
          description: Booking ID
          example: booking_abc
        departure_date:
          type: string
          format: date-time
          description: ISO 8601 departure datetime with timezone
          example: '2026-07-15T14:30:00+02:00'
        days_until_departure:
          type: integer
          description: Days remaining until departure
          example: 7
        esim_installed:
          type: boolean
          description: Whether the traveler has installed their eSIM
          example: false
    BookingAboutToDepartWebhookData:
      type: object
      properties:
        external_user_id:
          type: string
          description: Partner's own user identifier
          example: partner_user_456
        booking_id:
          type: string
          description: Booking ID
          example: booking_abc
        departure_date:
          type: string
          format: date-time
          description: ISO 8601 departure datetime with timezone
          example: '2026-07-15T14:30:00+02:00'
        hours_until_departure:
          type: integer
          description: Hours remaining until departure
          example: 2
        esim_installed:
          type: boolean
          description: Whether the traveler has installed their eSIM
          example: false
    PromoCodeRedeemedWebhookData:
      type: object
      properties:
        promocode:
          type: string
          description: The promo code that was redeemed
          example: SUMMER2026GR
        booking_id:
          type: string
          description: Booking ID
          example: booking_abc
        redeemed_at:
          type: string
          format: date-time
          description: When the promo code was redeemed
          example: '2026-07-10T12:00:00Z'
x-webhooks:
  PackageUsage20Percent:
    post:
      tags:
        - Webhooks
      summary: package.usage.20_percent
      description: >
        Triggered per package when 20% of the package's data has been consumed
        (data-limited) or 20% of the package's duration has elapsed
        (time-limited).
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: package.usage.20_percent
                    data:
                      $ref: '#/components/schemas/PackageUsageWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  PackageUsage50Percent:
    post:
      tags:
        - Webhooks
      summary: package.usage.50_percent
      description: >
        Triggered per package when 50% of the package's data has been consumed
        (data-limited) or 50% of the package's duration has elapsed
        (time-limited).
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: package.usage.50_percent
                    data:
                      $ref: '#/components/schemas/PackageUsageWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  PackageUsage80Percent:
    post:
      tags:
        - Webhooks
      summary: package.usage.80_percent
      description: >
        Triggered per package when 80% of the package's data has been consumed
        (data-limited) or 80% of the package's duration has elapsed
        (time-limited). This is the most effective trigger for top-up
        conversion.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: package.usage.80_percent
                    data:
                      $ref: '#/components/schemas/PackageUsageWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  EsimInstalled:
    post:
      tags:
        - Webhooks
      summary: esim.installed
      description: >-
        Triggered when the traveler has successfully installed the eSIM on their
        device.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: esim.installed
                    data:
                      $ref: '#/components/schemas/EsimStatusWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  EsimRemoved:
    post:
      tags:
        - Webhooks
      summary: esim.removed
      description: >
        Triggered when the traveler has removed the eSIM from their device. A
        removed eSIM can be re-installed — this event does not mean permanent
        loss of access.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: esim.removed
                    data:
                      $ref: '#/components/schemas/EsimStatusWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  PackageActivated:
    post:
      tags:
        - Webhooks
      summary: package.activated
      description: >
        Triggered when a package has been activated and the traveler can use
        data. Typically occurs when the traveler arrives at the destination and
        connects to a local network.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: package.activated
                    data:
                      $ref: '#/components/schemas/PackageActivatedWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  PromoCodeRedeemed:
    post:
      tags:
        - Webhooks
      summary: promo_code.redeemed
      description: Triggered when a traveler successfully redeems a promo code.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: promo_code.redeemed
                    data:
                      $ref: '#/components/schemas/PromoCodeRedeemedWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  BookingWithinCutoff:
    post:
      tags:
        - Webhooks
      summary: booking.within_cutoff
      description: >
        Triggered when the booking's departure date enters the configured cutoff
        window (default: 7 days). This is the optimal time for travelers to
        install their eSIM while still at home on Wi-Fi. The `esim_installed`
        field indicates whether the traveler has already installed — use it to
        decide whether to send an installation reminder or a confirmation.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: booking.within_cutoff
                    data:
                      $ref: '#/components/schemas/BookingWithinCutoffWebhookData'
      responses:
        '200':
          description: Webhook received successfully
  BookingAboutToDepart:
    post:
      tags:
        - Webhooks
      summary: booking.about_to_depart
      description: >
        Triggered when departure is imminent (default: 2 hours before the time
        in `departure_date`). This is the last-chance reminder for travelers who
        haven't installed their eSIM. Requires a full datetime with timezone in
        `departure_date` (e.g., `2026-07-15T14:30:00+02:00`) — if only a date
        string was provided, this event cannot fire.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    event:
                      example: booking.about_to_depart
                    data:
                      $ref: '#/components/schemas/BookingAboutToDepartWebhookData'
      responses:
        '200':
          description: Webhook received successfully
