openapi: 3.0.1
info:
  title: Hubby API
  version: 2.0.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:
                      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: Delete booking
      description: Deletes 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:
                    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: Delete booking by booking ID
      description: Deletes a booking using its external booking ID
      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'
  /countries/packages:
    get:
      tags:
        - Package
      summary: Get all packages grouped by country
      description: Retrieves all packages grouped by their respective countries
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackagesGroupedByCountryResponse'
        '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'
  /countries/{country}/packages:
    get:
      tags:
        - Package
      summary: Get packages for specific country
      description: Retrieves all packages for a specific country
      parameters:
        - name: country
          in: path
          required: true
          schema:
            type: string
          description: Country ISO code
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackagesGroupedBySingleCountryResponse'
        '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'
  /countries:
    get:
      tags:
        - Country
      summary: List all countries
      description: Retrieves a list of all available countries
      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/CountryResponse'
        '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'
  /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'
components:
  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
              traffic_policy:
                type: string
                nullable: true
                description: Traffic policy for the package
        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`, `traffic_policy`) 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:
                - US
                - CA
                - MX
          description: >-
            Country ISO3 code or IATA code, or an array for multi-destination.
            Optional — omit for destination-agnostic promo codes.
        traffic_policy:
          type: string
          description: >
            The traffic policy identifier. **Required** when `package_type` is
            `unlimited`. Not applicable for other package types.
          example: default_traffic_policy
      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`,
        `traffic_policy`) 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, `traffic_policy`: not applicable

        - **`time-limited`** — `size`: required, `package_duration`: required,
        `traffic_policy`: not applicable

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

        - **`starter`** — `size`: required, `package_duration`: required,
        `traffic_policy`: not applicable
    CountryResponse:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier of the country (ISO code)
          example: NLD
        name:
          type: string
          description: Full name of the country
          example: string
        has_esim:
          type: boolean
          description: Indicates whether this country supports eSIM.
        is_region:
          type: boolean
          nullable: true
          description: >-
            Indicates whether this entry represents a region (e.g. Europe, North
            America) rather than a single country. When true, the countries
            field contains the ISO codes of the countries included in this
            region.
        countries:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            List of ISO country codes included in this region. Only populated
            when is_region is true. Example: ['NLD', 'DEU', 'FRA'].
        i18n_name:
          type: object
          additionalProperties:
            type: string
          description: >-
            Localized country names keyed by locale code. Example: { "en":
            "Netherlands", "nl": "Nederland", "de": "Niederlande" }.
        imsi:
          type: integer
          nullable: true
          description: International Mobile Subscriber Identity
          example: 22
        LTE:
          type: boolean
          nullable: true
          description: Indicates if LTE is supported
          example: true
        apn:
          type: string
          nullable: true
          description: Access Point Name
          example: string
        global_network:
          type: string
          nullable: true
          description: Name of the global network
          example: string
        tier:
          type: integer
          nullable: true
          description: Indicates the tier level
          example: 2
        hubby:
          type: integer
          nullable: true
          description: Associated value, possibly a constant or identifier
          example: 4
        global_price:
          type: number
          nullable: true
          description: Price for global usage
          example: 1234
        click_count:
          type: integer
          nullable: true
          description: Number of clicks or accesses
          example: 12
        bokun_id:
          type: integer
          nullable: true
          description: Bokun platform ID
          example: 123456
    PackagesGroupedByCountryResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: Packages grouped by country fetched successfully.
        data:
          type: object
          additionalProperties:
            type: array
            items:
              type: object
              properties:
                id:
                  type: string
                  description: Unique identifier of the package
                days:
                  type: integer
                  description: Duration/validity of the package in days
                country:
                  type: string
                  description: ISO country code
                price:
                  type: number
                  format: float
                  description: Customer-facing price of the package
                type:
                  type: string
                  nullable: true
                  enum:
                    - data-limited
                    - time-limited
                    - starter
                    - unlimited
                  description: Package type
                is_active:
                  type: boolean
                  description: Whether the package is currently active
      example:
        success: true
        message: Packages grouped by country fetched successfully.
        data:
          TUR:
            - id: '340941'
              days: 365
              country: TUR
              price: 4.99
              type: data-limited
              is_active: true
    PackagesGroupedBySingleCountryResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: Packages grouped by country fetched successfully.
        data:
          type: object
          additionalProperties:
            type: array
            items:
              type: object
              properties:
                id:
                  type: string
                  description: Unique identifier of the package
                days:
                  type: integer
                  description: Duration/validity of the package in days
                country:
                  type: string
                  description: ISO country code
                price:
                  type: number
                  format: float
                  description: Customer-facing price of the package
                type:
                  type: string
                  nullable: true
                  enum:
                    - data-limited
                    - time-limited
                    - starter
                    - unlimited
                  description: Package type
                is_active:
                  type: boolean
                  description: Whether the package is currently active
      example:
        success: true
        message: Packages grouped by country fetched successfully.
        data:
          TUR:
            - id: '340941'
              days: 365
              country: TUR
              price: 4.99
              type: data-limited
              is_active: true
    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
