{
  "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.\n\nKey Features:\n- Create and manage eSIM bookings for your customers\n- Access our global package catalog with country-specific offerings\n- Track booking statuses and package activations\n- Support for multiple package types (starter, data-limited, unlimited, time-limited)\n\n## Package Types\n\nThe API supports four types of packages:\n\n- **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.\n\n- **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.\n\n- **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.\n\n- **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.\n\n**Note**: Top-ups are always data-limited packages, regardless of the original package type.\n\nAuthentication:\nAll API requests must include the following headers:\n- x-api-key: Your public API key\n- x-timestamp: Current Unix timestamp in milliseconds\n- x-signature: HMAC-SHA256 signature\n\nThe HMAC signature must be generated for each request using:\n1. Concatenate: timestamp + HTTP method + request path\n   Example: \"1678901234GET/bookings\"\n2. Generate HMAC-SHA256 using your secret key\n3. Convert to hex string\n\nNote: Swagger UI cannot be used to test the API directly as each request requires a unique HMAC signature.\nPlease implement the authentication in your client application.\n\nExample Node.js Implementation:\n```javascript\nconst cryptoJs = require('crypto-js');\n\n// Configuration values that would normally come from environment\nconst secretKey = \"YOUR_API_SECRET\";\nconst publicKey = \"YOUR_API_KEY\";\nconst baseUrl = \"YOUR_BASE_URL\";\n\n// Function to generate headers for API request\nfunction generateApiHeaders(method, path) {\n    //Timestamp is in milliseconds e.g. 1715558400000\n    const timestamp = Math.floor(Date.now()).toString();\n\n    // Ensure url is a string\n    let path = String(url);\n\n    // Remove baseUrl from the url if present\n    path = processedUrl.replace(baseUrl, '');\n\n    // Create query string if needed\n    const queryString = new URL(url).search;\n    if (queryString) {\n        processedUrl += queryString;\n    }\n\n    // Validate public key\n    if (!publicKey) {\n        throw new Error(\"Public key is required\");\n    }\n\n    // Create the payload\n    // Sample payload: 1715558400000GET/bookings?bookingId=1234567890\n    const payload = timestamp + method + path;\n\n    // Generate the HMAC signature\n    const signature = cryptoJs.HmacSHA256(payload, secretKey).toString(cryptoJs.enc.Hex);\n\n    // Return headers object\n    return {\n        'x-timestamp': timestamp,\n        'x-signature': signature,\n        'x-api-key': publicKey,\n        'Accept': 'application/json'\n    };\n}\n```\n\nNeed Help?\n- Technical Support: support@hubbyesim.com\n"
  },
  "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.\nThe response is always fresh and not cached (uses Cache-Control headers).\n",
        "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'.\n"
                  },
                  "return_date": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601 return date. Must be after `departure_date`.\nOptional if not provided by the partner.\n"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Traveler's email. Required if `booking_id` is not provided.\n"
                  },
                  "booking_id": {
                    "type": "string",
                    "minLength": 3,
                    "description": "Booking ID. Required if `email` is not provided.\n"
                  },
                  "external_id": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 100,
                    "nullable": true,
                    "description": "External ID Usable by your organization to further identify the booking (optional).\n"
                  },
                  "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.\n",
                    "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.\n"
                  },
                  "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.\n"
                  },
                  "package_specifications": {
                    "type": "array",
                    "minItems": 1,
                    "description": "A list of package specifications. At least one entry is required.\nEach 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.\n",
                    "items": {
                      "type": "object",
                      "properties": {
                        "external_user_id": {
                          "type": "string",
                          "description": "Your own identifier for the traveler receiving this package.\n\n**⚠️ 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.\n\n- **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).\n- **Without `external_user_id` (traditional flow):** The package follows the standard booking flow. The field is ignored.\n",
                          "example": "user_abc123"
                        },
                        "destination": {
                          "oneOf": [
                            {
                              "type": "string",
                              "pattern": "^[A-Z]{2,3}$",
                              "description": "Single destination code (ISO 2-3 uppercase letters).\nExample: 'US', 'NL', 'GRC', 'XA'.\n"
                            },
                            {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "pattern": "^[A-Z]{2,3}$"
                              },
                              "minItems": 1,
                              "description": "Array of destination codes for multi-destination bookings.\nThe system will optimize ESIM selection to cover all destinations with the fewest ESIMs.\nExample: ['US', 'CA', 'MX'] for North American trip.\n"
                            }
                          ],
                          "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)\n"
                        },
                        "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. \n- `data-limited`: Traditional packages with specific data allowance (default)\n- `time-limited`: Unlimited data for a specific duration\n- `starter`: Both data and time limited packages\n"
                        },
                        "package_duration": {
                          "type": "integer",
                          "minimum": 1,
                          "default": 365,
                          "description": "Duration of the package in days. \n- Defaults to 365 days for data-limited packages\n- Defaults to 2 days for starter and time-limited packages\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "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.\n\nThe 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.\n\nThe booking record is **not deleted** from the database.\n",
        "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.\n\nThe 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.\n\nThe booking record is **not deleted** from the database.\n",
        "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.\nPromo codes that have already been used are **skipped**; the response includes `updated_count` and `skipped_count`.\n\n**Authentication:** HMAC keys.\n",
        "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.\n",
            "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.\nMessages: `\"booking_id is required\"`, `\"Package not found\"`.\n",
            "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.\nIf that promo code is already redeemed, the API returns **400 Bad Request**.\n\n**Authentication:** HMAC keys.\n",
        "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.\nMessages: `\"booking_id is required\"`, `\"Invalid uuid\"`, `\"Package not found\"`, `\"Promo code has already been redeemed and cannot be updated\"`.\n",
            "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.\nMessages: `\"Booking not found!\"`, `\"Promo code not found!\"`.\n",
            "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.\nEach destination includes localized names, ISO3 country codes, and metadata such as type and tier.\n",
        "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).\nUse this endpoint to get detailed information about a specific country or region and the eSIM packages available for it.\n",
        "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.\n\n**Authorization:**\n- Admins can access any promo code\n- Partners can only access promo codes belonging to their organization\n",
        "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.\nThe user receives the promo code via email immediately after claiming.\n",
        "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.\n\n**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.\n\n**Authentication:** HMAC-SHA256 (same as all partner endpoints).\n",
        "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.\n",
                    "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.\n",
                    "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.\n\n**Called automatically by the Hubby WebView** — partners do not call this endpoint directly. Documented for transparency and to aid debugging of WebView network traffic.\n\n**Authentication:** None (the redirect token itself is the credential).\n",
        "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.\n\n**Called automatically by the Hubby WebView** — partners do not call this endpoint directly. Documented for transparency and to aid debugging of WebView network traffic.\n\n**Authentication:** Bearer JWT from `/webapp/auth/exchange`.\n",
        "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.\n\nUse 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.\n\n**Authentication:** Bearer JWT from `/webapp/auth/exchange`.\n",
        "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.\n\nThis is the central endpoint for the Native eSIM Integration. One call gives you the traveler's full state.\n\n**When to call:**\n- Every time the traveler opens the eSIM section\n- After a top-up purchase (to confirm the new package appeared)\n- After redeeming a package queue\n- Periodically for data meter refresh (or rely on webhooks)\n\n**Authentication:** HMAC-SHA256 (same as all partner endpoints).\n",
        "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:\n- `RELEASED` — profile downloaded and ready\n- `INSTALLED` — profile installed on device\n- `ENABLED` — profile active and connected\n- `Local Enable` — profile locally enabled on device\n- `Local Disable` — profile locally disabled on device\n",
                              "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:\n- `ACTIVE` — currently usable\n- `NOT_ACTIVE` — assigned but not yet activated\n- `DEPLETED` — data exhausted or expired\n",
                                "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.\n\nThe 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.\n\nYou do not need to hard-code or maintain installation content in your app — Hubby keeps instructions current for every device and OS version.\n\n**Authentication:** HMAC-SHA256 (same as all partner endpoints).\n",
        "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.\n\n**Authentication:** HMAC-SHA256 (same as all partner endpoints).\n",
        "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.\n\n**Authentication:** HMAC-SHA256 (same as all partner endpoints).\n",
        "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.\n\nUse the `package_queue_id` from the user dashboard response (`GET /native/user-dashboard`) to identify which package queue to redeem.\n\n**Authentication:** HMAC-SHA256 (same as all partner endpoints).\n",
        "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.\n"
          },
          "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.\n"
          },
          "package_specifications": {
            "type": "array",
            "minItems": 1,
            "description": "A list of package specifications. At least one entry is required.\nEach 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.\n",
            "items": {
              "type": "object",
              "properties": {
                "destination": {
                  "oneOf": [
                    {
                      "type": "string",
                      "pattern": "^[A-Z]{2,3}$",
                      "description": "Single destination code (ISO 2-3 uppercase letters).\nExample: 'US', 'NL'.\n"
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "pattern": "^[A-Z]{2,3}$"
                      },
                      "minItems": 1,
                      "description": "Array of destination codes for multi-destination bookings.\nThe system will optimize ESIM selection to cover all destinations with the fewest ESIMs.\nExample: ['US', 'CA', 'MX'] for North American trip.\n"
                    }
                  ],
                  "description": "Destination code(s) for the booking. Can be a single destination or an array of destinations for multi-destination trips with ESIM optimization.\n"
                },
                "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;\nother fields in the same specification are not used for resolution and are effectively ignored.\n"
                },
                "package_type": {
                  "type": "string",
                  "enum": [
                    "starter",
                    "data-limited",
                    "unlimited",
                    "time-limited"
                  ],
                  "default": "data-limited",
                  "description": "Type of package to create. \n- `starter`: Small data allowance for short duration, ideal for trials (default: 2 days)\n- `data-limited`: Traditional packages with specific data allowance (default: 365 days)\n- `unlimited`: Unrestricted data usage with fair use policy\n- `time-limited`: Fixed data allowance with time constraint for full-speed access\n"
                },
                "package_duration": {
                  "type": "integer",
                  "minimum": 1,
                  "default": 365,
                  "description": "Duration of the package in days. \n- Defaults to 365 days for data-limited packages\n- Defaults to 2 days for starter and time-limited packages\n"
                }
              }
            }
          }
        },
        "description": "Booking request object. Either `email` or `booking_id` is required. The `departure_date` and `package_specifications` are mandatory.\n"
      },
      "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.\n"
          },
          "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.\n"
          }
        }
      },
      "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.\n\n**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.\n\n**When `package_id` is provided with `package_type`:** The explicit fields from the request body are used instead of the package's own properties.\n"
          },
          "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.\n- `starter`: Small data allowance for short duration, ideal for trials\n- `data-limited`: Traditional packages with specific data allowance\n- `unlimited`: Unrestricted data usage with fair use policy\n- `time-limited`: Fixed data allowance with time constraint for full-speed access\n"
          },
          "size": {
            "type": "string",
            "pattern": "^[0-9]\\d*(\\.\\d+)?(MB|GB)$",
            "description": "Package size (e.g. `\"1GB\"`, `\"5GB\"`).\nRequired when `package_type` is `data-limited`, `time-limited`, or `starter`. Not required for `unlimited`.\n"
          },
          "package_duration": {
            "type": "integer",
            "minimum": 1,
            "description": "Duration of the package in days.\nRequired when `package_type` is `time-limited`, `unlimited`, or `starter`. Not required for `data-limited`.\n"
          },
          "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.\n\n**When only `package_id` is sent:** The package's properties (`package_type`, `size`, `package_duration`, `destination`) are resolved automatically from the referenced package.\n\n**When `package_id` is sent with `package_type`:** The explicit fields from the request body are used instead of the package's own properties.\n\n**When only `package_type` is sent:** The conditional field requirements apply based on the type.\n\n**Package type requirements:**\n\n- **`data-limited`** — `size`: required, `package_duration`: not required\n- **`time-limited`** — `size`: required, `package_duration`: required\n- **`unlimited`** — `size`: not required, `package_duration`: required\n- **`starter`** — `size`: required, `package_duration`: required\n"
      },
      "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:\n- \"Missing required HMAC authentication headers\" - When x-api-key, x-timestamp, or x-signature are missing\n- \"Request timestamp expired\" - When timestamp is more than 400 minutes old or in future\n- \"Invalid signature\" - When HMAC signature verification fails\n\nHMAC Signature Generation:\n1. Create message string by concatenating:\n   `${timestamp}${method}${path}?${queryString}`\n   Example: \"1678901234GET/bookings?perPage=10\"\n2. Generate HMAC-SHA256 using your API secret\n3. Convert to hex string\n\nNotes:\n- Timestamp must be within 400 minutes of current time\n- API keys expire after 1 year\n- Query parameters must be included in signature if present\n",
        "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.\nFor data-limited packages, thresholds are based on data consumption.\nFor time-limited packages, thresholds are based on duration elapsed.\n",
        "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).\n",
        "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).\n",
        "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.\n",
        "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.\n",
        "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.\n",
        "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.\n",
        "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.\n",
        "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"
          }
        }
      }
    }
  }
}