{
  "openapi": "3.1.0",
  "info": {
    "title": "Hubby Partner API",
    "version": "5.20.0",
    "description": "Every response of a matched route carries the header `X-Hubby-Call-Id`, the id of the call in Hubby's records. A request refused before it reaches a route carries no call id: an unknown path (`404`, `code` `not_found`), a method the path does not take (`405`, `code` `method_not_allowed`), or a query string PHP cannot parse whole (`400`, `code` `malformed_query_string`). Those two refusals, `405` and `400`, can answer any path and are not listed per operation. Every `4xx` refusal body carries a `message` and a `code` to branch on. A `5xx` can carry a `message` alone: the issue call's `500` (`POST /v1/bookings/{booking}/items/{item}/package`) has no `code`; retry it with the same `Idempotency-Key`."
  },
  "servers": [
    {
      "url": "https://core.hubbyesim.com/api",
      "description": "Production"
    },
    {
      "url": "https://core-staging.hubbyesim.com/api",
      "description": "Staging"
    }
  ],
  "security": [
    {
      "partnerKey": [],
      "partnerSignature": [],
      "partnerTimestamp": []
    }
  ],
  "paths": {
    "/v1/analytics/summary": {
      "get": {
        "operationId": "partner_api.v1.analytics.summary",
        "description": "Your headline figures for a date range: bookings, customers, upcoming\ndepartures, the 14-day activation rate, net revenue per currency, data\nconsumed and packages that started using data. Set `compare` to add the\nsame totals for the preceding period of equal length.\n\nWith a key that has key inheritance, the figures also include every\ndescendant of your own Partner that has moved to this platform, and\n`by_partner` splits them per Partner. Name descendants in `partner_ids`\nto narrow to them, and set `include_self=0` to leave your own Partner\nout. Descendants not yet on this platform are listed in\n`meta.excluded_partners` and counted in no figure. A key without key\ninheritance reads your own Partner only, and refuses both parameters.\nParameters that leave no Partner on this platform in scope answer 422\nwith `code` `no_partner_in_scope`.\n\nFigures may be up to 15 minutes old: a repeated request is answered\nfrom the same computation, and `meta.computed_at` says when it ran.\n\nOnly available once your own Partner has moved to this platform; before\nthat it answers 403. When a range takes too long to compute it answers\n503 with a `Retry-After` header: retry later or choose a shorter range.",
        "summary": "Analytics summary",
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "The first day of the range, `Y-m-d`, a UTC calendar day. Send `from` and `to` together, or neither: with neither, the range is the last 30 days, today included. A malformed value is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "The last day of the range, `Y-m-d`, a UTC calendar day on or after `from`, both ends included. The range may cover at most 366 days; a longer one is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "partner_ids[]",
            "in": "query",
            "description": "The descendants to narrow the figures to, by Partner id. Only with key inheritance: a key without it is refused with 422. An id that is not one of your descendants is refused with 422.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 64
              }
            }
          },
          {
            "name": "include_self",
            "in": "query",
            "description": "Set to `0` to leave your own Partner out of the figures. Only with key inheritance: a key without it is refused with 422. Default `1`.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "compare",
            "in": "query",
            "description": "Set to `1` (or `true`) to add `comparison`: the same totals for the preceding period of the same length, ending the day before `from`. Any value that is not a boolean is refused with 422.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`AnalyticsSummary`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AnalyticsSummary"
                    },
                    "meta": {
                      "description": "What the figures cover.",
                      "$ref": "#/components/schemas/AnalyticsMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Your own Partner has not moved to this platform yet, so its figures are still on the previous platform. `code`: `partner_not_cut_over`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "partner_not_cut_over"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "A query parameter failed validation: a malformed date or a range over 366 days, `partner_ids` or `include_self=0` from a key without key inheritance, a `partner_ids` entry that is not one of your descendants (the message names it), or a scope with no Partner that has moved to this platform left in it (`code` is `no_partner_in_scope`). `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "errors": {
                      "type": "object",
                      "description": "The messages for each parameter that failed, keyed by parameter name.",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on: `no_partner_in_scope` when the parameters left no Partner that has moved to this platform in scope, `validation_failed` for every other failure.",
                      "enum": [
                        "validation_failed",
                        "no_partner_in_scope"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many analytics requests: the limit is 60 requests a minute per Partner, apart from your booking traffic. Retry after the number of seconds in `Retry-After`. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The figures took too long to compute. Retry after the number of seconds in `Retry-After`, or choose a shorter range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "analytics_timed_out"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/funnel": {
      "get": {
        "operationId": "partner_api.v1.analytics.funnel",
        "description": "The journey from booking codes issued to data used, for the bookings\ncreated in the range: every booking code of those bookings, followed\nthrough being claimed, paid for, eSIM installed, package activated and\nfirst data used, whenever each of those happened. Each step has its\ncount, its rate against the previous step and its share of the codes\nissued. Set `dimension` to also split the funnel by destination, bundle\nsize, sales channel, booking source or transaction type; the groups add\nup to the funnel.\n\nThe funnel covers new bookings only: bookings moved over from the\nprevious platform and test bookings are left out (and counted in\n`cohort`), so its codes issued can differ from the summary's bookings.\nIts \"package activated\" step has no 14-day window and follows bookings\nfrom the day they were created, so it is a different figure from the\nsummary's activation rate.\n\nScoped, cached and refused exactly as the summary: key inheritance\n(`partner_ids`, `include_self`), descendants not yet on this platform\nin `meta.excluded_partners`, figures up to 15 minutes old (see\n`meta.computed_at`), 403 before your own Partner has moved to this\nplatform, and 503 with `Retry-After` when a range takes too long to\ncompute.",
        "summary": "Analytics funnel",
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "The first day of the range, `Y-m-d`, a UTC calendar day. Send `from` and `to` together, or neither: with neither, the range is the last 30 days, today included. A malformed value is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "The last day of the range, `Y-m-d`, a UTC calendar day on or after `from`, both ends included. The range may cover at most 366 days; a longer one is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "partner_ids[]",
            "in": "query",
            "description": "The descendants to narrow the figures to, by Partner id. Only with key inheritance: a key without it is refused with 422. An id that is not one of your descendants is refused with 422.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 64
              }
            }
          },
          {
            "name": "include_self",
            "in": "query",
            "description": "Set to `0` to leave your own Partner out of the figures. Only with key inheritance: a key without it is refused with 422. Default `1`.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "dimension",
            "in": "query",
            "description": "Break the funnel down by this dimension: `destination`, `bundle_size`, `sales_channel`, `booking_source` or `transaction_type`. Any other value is refused with 422. Leave it out for no breakdown.",
            "schema": {
              "type": "string",
              "enum": [
                "destination",
                "bundle_size",
                "sales_channel",
                "booking_source",
                "transaction_type"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`AnalyticsFunnel`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AnalyticsFunnel"
                    },
                    "meta": {
                      "description": "What the figures cover.",
                      "$ref": "#/components/schemas/AnalyticsMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Your own Partner has not moved to this platform yet, so its figures are still on the previous platform. `code`: `partner_not_cut_over`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "partner_not_cut_over"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "A query parameter failed validation: a malformed date or a range over 366 days, `partner_ids` or `include_self=0` from a key without key inheritance, a `partner_ids` entry that is not one of your descendants (the message names it), or a scope with no Partner that has moved to this platform left in it (`code` is `no_partner_in_scope`). `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "errors": {
                      "type": "object",
                      "description": "The messages for each parameter that failed, keyed by parameter name.",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on: `no_partner_in_scope` when the parameters left no Partner that has moved to this platform in scope, `validation_failed` for every other failure.",
                      "enum": [
                        "validation_failed",
                        "no_partner_in_scope"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many analytics requests: the limit is 60 requests a minute per Partner, apart from your booking traffic. Retry after the number of seconds in `Retry-After`. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The figures took too long to compute. Retry after the number of seconds in `Retry-After`, or choose a shorter range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "analytics_timed_out"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/timeseries": {
      "get": {
        "operationId": "partner_api.v1.analytics.timeseries",
        "description": "Your figures over time, for charts: bookings created, bookings\ndeparting, bookings cancelled, customers gained, net revenue per\ncurrency and data consumed, one row per `bucket` (`day`, the default,\n`week` or `month`). Every bucket in the range is present, zero when\nnothing happened in it. Buckets are UTC.\n\nThe figures are summed over every Partner in `meta.partner_ids`, with\nno per-Partner split (the summary has one).\n\nScoped, cached and refused exactly as the summary: key inheritance\n(`partner_ids`, `include_self`), descendants not yet on this platform\nin `meta.excluded_partners`, figures up to 15 minutes old (see\n`meta.computed_at`), 403 before your own Partner has moved to this\nplatform, and 503 with `Retry-After` when a range takes too long to\ncompute.",
        "summary": "Analytics time series",
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "The first day of the range, `Y-m-d`, a UTC calendar day. Send `from` and `to` together, or neither: with neither, the range is the last 30 days, today included. A malformed value is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "The last day of the range, `Y-m-d`, a UTC calendar day on or after `from`, both ends included. The range may cover at most 366 days; a longer one is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "partner_ids[]",
            "in": "query",
            "description": "The descendants to narrow the figures to, by Partner id. Only with key inheritance: a key without it is refused with 422. An id that is not one of your descendants is refused with 422.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 64
              }
            }
          },
          {
            "name": "include_self",
            "in": "query",
            "description": "Set to `0` to leave your own Partner out of the figures. Only with key inheritance: a key without it is refused with 422. Default `1`.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "bucket",
            "in": "query",
            "description": "The length of each row: `day` (the default), `week` (ISO weeks, starting Monday) or `month`, all in UTC. Any other value is refused with 422.",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "week",
                "month"
              ],
              "default": "day"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`AnalyticsTimeseries`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AnalyticsTimeseries"
                    },
                    "meta": {
                      "description": "What the figures cover.",
                      "$ref": "#/components/schemas/AnalyticsMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Your own Partner has not moved to this platform yet, so its figures are still on the previous platform. `code`: `partner_not_cut_over`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "partner_not_cut_over"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "A query parameter failed validation: a malformed date or a range over 366 days, `partner_ids` or `include_self=0` from a key without key inheritance, a `partner_ids` entry that is not one of your descendants (the message names it), or a scope with no Partner that has moved to this platform left in it (`code` is `no_partner_in_scope`). `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "errors": {
                      "type": "object",
                      "description": "The messages for each parameter that failed, keyed by parameter name.",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on: `no_partner_in_scope` when the parameters left no Partner that has moved to this platform in scope, `validation_failed` for every other failure.",
                      "enum": [
                        "validation_failed",
                        "no_partner_in_scope"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many analytics requests: the limit is 60 requests a minute per Partner, apart from your booking traffic. Retry after the number of seconds in `Retry-After`. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The figures took too long to compute. Retry after the number of seconds in `Retry-After`, or choose a shorter range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "analytics_timed_out"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/breakdowns": {
      "get": {
        "operationId": "partner_api.v1.analytics.breakdowns",
        "description": "Your codes issued in a date range by destination and by bundle size,\nand the discounts given per discount code and currency. Each section\ncounts records (codes, discounted payments), and its rows add up to that\ncount. The figures are summed over every Partner in\n`meta.partner_ids`, with no per-Partner split (the summary has one).\n\nScoped, cached and refused exactly as the summary: key inheritance\n(`partner_ids`, `include_self`), descendants not yet on this platform\nin `meta.excluded_partners`, figures up to 15 minutes old (see\n`meta.computed_at`), 403 before your own Partner has moved to this\nplatform, and 503 with `Retry-After` when a range takes too long to\ncompute.",
        "summary": "Analytics breakdowns",
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "The first day of the range, `Y-m-d`, a UTC calendar day. Send `from` and `to` together, or neither: with neither, the range is the last 30 days, today included. A malformed value is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "The last day of the range, `Y-m-d`, a UTC calendar day on or after `from`, both ends included. The range may cover at most 366 days; a longer one is refused with 422.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "partner_ids[]",
            "in": "query",
            "description": "The descendants to narrow the figures to, by Partner id. Only with key inheritance: a key without it is refused with 422. An id that is not one of your descendants is refused with 422.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 64
              }
            }
          },
          {
            "name": "include_self",
            "in": "query",
            "description": "Set to `0` to leave your own Partner out of the figures. Only with key inheritance: a key without it is refused with 422. Default `1`.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`AnalyticsBreakdowns`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AnalyticsBreakdowns"
                    },
                    "meta": {
                      "description": "What the figures cover.",
                      "$ref": "#/components/schemas/AnalyticsMeta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Your own Partner has not moved to this platform yet, so its figures are still on the previous platform. `code`: `partner_not_cut_over`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "partner_not_cut_over"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "A query parameter failed validation: a malformed date or a range over 366 days, `partner_ids` or `include_self=0` from a key without key inheritance, a `partner_ids` entry that is not one of your descendants (the message names it), or a scope with no Partner that has moved to this platform left in it (`code` is `no_partner_in_scope`). `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "errors": {
                      "type": "object",
                      "description": "The messages for each parameter that failed, keyed by parameter name.",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on: `no_partner_in_scope` when the parameters left no Partner that has moved to this platform in scope, `validation_failed` for every other failure.",
                      "enum": [
                        "validation_failed",
                        "no_partner_in_scope"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many analytics requests: the limit is 60 requests a minute per Partner, apart from your booking traffic. Retry after the number of seconds in `Retry-After`. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The figures took too long to compute. Retry after the number of seconds in `Retry-After`, or choose a shorter range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "A human-readable explanation."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "analytics_timed_out"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings": {
      "post": {
        "operationId": "partner_api.v1.bookings.store",
        "description": "A package specification is a request, not a catalog selection: an\naccepted `size` or `package_duration` that no bundle carries exactly is\nfulfilled at redemption by the closest bundle of the same package type\nat the same destination. Only a specification no amount of closeness\ncan satisfy is refused — naming a `package_type` the destination does\nnot sell at all answers 422 with a\n`package_specifications.{index}.package_type` error.",
        "summary": "Create a booking for the authenticated partner",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBookingRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "`Booking`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_in_use"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The payload failed validation, a package specification named a `package_type` its destination sells no active bundle of — the one specification Closest match cannot answer — or a package specification named `starter` would give a traveller a third starter package for that destination from you (at most two). Starters not yet issued, starters issued (also when later cancelled, or upgraded to another package type) and starters earlier in the same request count; a starter cancelled before it was issued does not. The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "enum": [
                            "validation_failed"
                          ]
                        }
                      },
                      "required": [
                        "message",
                        "errors",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "partner_api.v1.bookings.index",
        "description": "With `format=csv`, the response is every booking that departs from `departure_from` to `departure_to` (both then required), in departure order, up to 5000 bookings, and not one page. The response is JSON in both cases. `pagination.hasNextPage` is then `true` when more than 5000 bookings matched: send a shorter date range.",
        "summary": "List your bookings, one cursor page at a time",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "per_page",
            "in": "query",
            "description": "How many bookings one page holds, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 10,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The page to read: a `pagination.nextCursor` or `pagination.prevCursor` from the previous page, sent back unchanged. Omit it for the first page.",
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Only bookings whose `email` is this address; letter case does not matter.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "email",
              "maxLength": 255
            }
          },
          {
            "name": "external_reference",
            "in": "query",
            "description": "Only bookings that carry exactly this `external_reference`; every booking that carries it is listed, not only the newest.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255
            }
          },
          {
            "name": "depart_at",
            "in": "query",
            "description": "Only bookings that depart on this day, `YYYY-MM-DD`. Matches the whole day in UTC, not an instant: a time sent with the day is ignored.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "created_at",
            "in": "query",
            "description": "Only bookings created on this day, `YYYY-MM-DD`. Matches the whole day in UTC, not an instant: a time sent with the day is ignored.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "promo_code",
            "in": "query",
            "description": "Only bookings with an item whose `code` is this value.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255
            }
          },
          {
            "name": "destination",
            "in": "query",
            "description": "Only bookings with an item for this destination: an ISO 3166-1 alpha-2 or alpha-3 code, a region code or a destination name, matched on any item of the booking. With this filter every matching booking comes in one answer, sorted as asked, and `pagination` carries no cursors; `per_page` and `cursor` are ignored.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 120
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "The column to sort by.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "depart_at",
                "external_reference"
              ],
              "default": "created_at"
            }
          },
          {
            "name": "order",
            "in": "query",
            "description": "The sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "`csv` switches to the export answer: every booking that departs from `departure_from` to `departure_to`, both then required, in departure order, up to 5000 bookings in one JSON answer. No CSV file is produced; the body has the same shape as a page, with `pagination.hasNextPage` only.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "enum": [
                "csv",
                null
              ]
            }
          },
          {
            "name": "departure_from",
            "in": "query",
            "description": "With `format=csv`: the first departure day to include, `YYYY-MM-DD`. Required then, ignored otherwise.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          },
          {
            "name": "departure_to",
            "in": "query",
            "description": "With `format=csv`: the last departure day to include, `YYYY-MM-DD`, on or after `departure_from`. Required then, ignored otherwise.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Booking"
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "perPage": {
                              "type": "integer"
                            },
                            "nextCursor": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "prevCursor": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "hasNextPage": {
                              "type": "boolean"
                            },
                            "hasPrevPage": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "perPage",
                            "nextCursor",
                            "prevCursor",
                            "hasNextPage",
                            "hasPrevPage"
                          ]
                        }
                      },
                      "required": [
                        "data",
                        "pagination"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Booking"
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "hasNextPage": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "hasNextPage"
                          ]
                        }
                      },
                      "required": [
                        "data",
                        "pagination"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "A query parameter failed validation: `per_page` outside 1 to 100, a malformed date, `format=csv` without both `departure_from` and `departure_to`, or `departure_to` before `departure_from`. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "validation_failed"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/imports/presign-upload": {
      "post": {
        "operationId": "partner_api.v1.bookings.imports.presign-upload",
        "description": "PUT the CSV to `upload_url` with every header in `required_headers`, before `expires_at`: the URL is valid for 15 minutes. `bucket` and `object_path` name where the file lands, under your own account and the `dataset` and `date` you sent. Hubby reads the file and creates its bookings.",
        "summary": "Get a presigned URL to upload a booking dataset file",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PresignBookingUploadData"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bucket": {
                      "type": "string"
                    },
                    "object_path": {
                      "type": "string"
                    },
                    "upload_url": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string"
                    },
                    "required_headers": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "bucket",
                    "object_path",
                    "upload_url",
                    "expires_at",
                    "required_headers"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A file already exists for this dataset and day and `overwrite` is not `true` (`upload_already_exists`): send `overwrite: true` to replace it. A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "upload_already_exists"
                        },
                        "object_path": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "message",
                        "code",
                        "object_path"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_in_use"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "`date` is missing or not `Y-m-d`, `dataset` is not letters, digits, `_` and `-` up to 64 characters, or `overwrite` is not a boolean. The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "enum": [
                            "validation_failed"
                          ]
                        }
                      },
                      "required": [
                        "message",
                        "errors",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many presign requests from your account: 30 per minute. Retry after the `Retry-After` header. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{bookingId}/package-specifications": {
      "patch": {
        "operationId": "partner_api.v1.bookings.update-package-specifications",
        "description": "Items whose package is already issued are skipped and counted in `skipped_count`, which is `0` when every item changed. `{bookingId}` accepts the same identifiers as `GET /v1/bookings/{id}`. To change one item only, send the same body to `PATCH /v1/bookings/{bookingId}/package-specifications/{uuid}`.",
        "summary": "Change the package specification of every item of a booking",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "bookingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePackageSpecificationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_count": {
                      "type": "integer"
                    },
                    "skipped_count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated_count",
                    "skipped_count"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking does not exist, or is not yours. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_in_use"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The payload failed validation, or the change was refused for one item and no item changed: `destination` matches no destination you can sell (`destination`); `bundle_id` is outside your catalogue, your account settings choose the bundle, or Hubby support granted the item (`bundle_id`); or the change would give a traveller a third Starter package for one destination from you — by making an item a Starter, or by moving a Starter, or a package upgraded from one, to another destination (`package_type`; Starters count as on `POST /v1/bookings`). The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "enum": [
                            "validation_failed"
                          ]
                        }
                      },
                      "required": [
                        "message",
                        "errors",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer. Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{bookingId}/package-specifications/{uuid}": {
      "patch": {
        "operationId": "partner_api.v1.bookings.update-package-specification",
        "description": "An item whose package is already issued is refused with 400, so `skipped_count` is always `0` here. `{bookingId}` accepts the same identifiers as `GET /v1/bookings/{id}`.",
        "summary": "Change the package specification of one booking item",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "bookingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "description": "The item to change: its `id` in the booking's `items` (`items[].id`), not its `offer_uuid`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePackageSpecificationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_count": {
                      "type": "integer",
                      "const": 1
                    },
                    "skipped_count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated_count",
                    "skipped_count"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "The item already has a package issued (`package_specification_already_redeemed`); terminate it first, or change another item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking, or the package specification `uuid` names, does not exist or is not yours. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_in_use"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The payload failed validation, or the change was refused and the item did not change: `destination` matches no destination you can sell (`destination`); `bundle_id` is outside your catalogue, your account settings choose the bundle, or Hubby support granted the item (`bundle_id`); or the change would give the traveller a third Starter package for one destination from you — by making the item a Starter, or by moving a Starter, or a package upgraded from one, to another destination (`package_type`; Starters count as on `POST /v1/bookings`). The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "enum": [
                            "validation_failed"
                          ]
                        }
                      },
                      "required": [
                        "message",
                        "errors",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer. Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{id}": {
      "get": {
        "operationId": "partner_api.v1.bookings.show",
        "description": "`{id}` is the booking's `id`, its id in the legacy API, or your own `external_reference`, tried in that order.",
        "summary": "Read one booking",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking does not exist, or is not yours. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer. Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "partner_api.v1.bookings.update",
        "description": "Changing `depart_at` or `return_at` reschedules the booking. If Hubby sends pre-departure emails for your account, they follow the new dates. A refused change writes nothing, including the other fields sent with it.",
        "summary": "Partially update a booking: an omitted field is left as it is, and an explicit `null` clears it",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBookingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking does not exist, or is not yours. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_in_use"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The payload failed validation, or the trip dates could not be moved: the booking has already departed (`depart_at`), the new departure is before today (`depart_at`), `depart_at` is `null` for a booking that has a departure, the return is before the departure, the booking is cancelled (`booking`), or another of your live bookings already uses this `external_reference` on that departure (`external_reference`). The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "enum": [
                            "validation_failed"
                          ]
                        }
                      },
                      "required": [
                        "message",
                        "errors",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer. Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "partner_api.v1.bookings.destroy",
        "description": "`{id}` accepts the same identifiers as `GET /v1/bookings/{id}`. A booking that cannot be cancelled is refused with 403 and a `code`; `is_cancellable` on every booking read tells you before you call. A repeat `DELETE` of a cancelled booking answers 200 and changes nothing.\n\nAn issued package that has not activated can be terminated first (`DELETE /v1/bookings/{booking}/items/{item}/package`), after which the cancel goes through. Once a package has activated on the network, or when an item was brought over from the previous backend already redeemed, the booking can no longer be cancelled.",
        "summary": "Cancel a booking and remove the traveller's personal data from it",
        "tags": [
          "Booking"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "const": "Booking cancelled successfully."
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The booking cannot be cancelled: it is a Membership booking, which its Membership period owns (`booking_is_membership`); it has departed (`booking_departed`); an item holds an issued package that is not terminated (`booking_has_redeemed_promo_codes`: \"promo code\" is the previous backend's name for an item's code, kept so existing integrations still match; terminate the package first, which is possible only before it activates); or a traveller paid for a package on it (`booking_has_paid_packages`: a payment still pending and a paid upgrade of an item count, a top-up bought on the eSIM does not).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking does not exist, or is not yours. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_in_use"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_reused"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer. Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bookings/{booking}/items/{item}/package": {
      "post": {
        "operationId": "partner_api.v1.bookings.items.package.store",
        "description": "Creates the package on the network and, for a traveller with no Hubby eSIM yet, assigns one. The response holds everything the traveller needs to install. A traveller who already has a Hubby eSIM gets the new package on that same eSIM: the ICCID repeats and `esim.installed_at` is set once it is on their phone, so do not ask them to install again.\n\n`{booking}` accepts the same identifiers as `GET /v1/bookings/{id}`; `{item}` is the item's `id`. Send an `Idempotency-Key`: a retry with the same key replays the first `201` unchanged, with `Idempotent-Replayed: true`, so retry blind after any timeout. The key is stored with a `201` only, so a `5xx` answer is safe to retry with the same key. Once you terminate the package, its key is spent: sending it again answers `409` `idempotency_key_spent`, and issuing again needs a new key. Issuing calls the network provider inline and is rate limited per Partner; honour `429` and its `Retry-After`.",
        "summary": "Issue the eSIM package for a booking item",
        "tags": [
          "BookingItemPackage"
        ],
        "parameters": [
          {
            "name": "booking",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "item",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. A unique key for this issue, for example a UUID, reused across every retry of it. A retry with the same key replays the stored response for 24 hours, until you terminate the package it issued.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssuePackageRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "`IssuedPackage`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IssuedPackage"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "No `Idempotency-Key` header was sent (`idempotency_key_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking or the item does not exist, or is not yours. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "The item already has a package (`package_already_issued`; its `Location` header points at the package to read), the booking is cancelled (`booking_cancelled`), the item is not `queued` (`item_not_queued`; an `unavailable` item cannot be issued now), the traveller already has two Starter packages issued for this destination from you, counting those since cancelled or upgraded to another package type — the most allowed (`starter_cap_exceeded`), the item changed while the call ran, for example a cancel (`provisioning_target_changed`; read the item and decide again), the item can no longer be redeemed (`package_queue_not_redeemable`), or the `Idempotency-Key` issued a package you have since terminated (`idempotency_key_spent`; do not retry, send a new key to issue again). A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_in_use"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Sent with `package_already_issued` only: the URL of the package to read.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The item names no traveller and no `external_user_id` was sent, or names a different one (`validation_failed` on `external_user_id`); the traveller named now by `external_user_id` already has two Starter packages for this destination from you (`validation_failed` on `external_user_id`; an item that already named its traveller answers `409` `starter_cap_exceeded` instead); the item was booked without a destination, which only a traveller who redeems in the Hubby app can pick (`destination_required`; only an item booked while `GET /v1/me` reported `force_destinationless: true`, or booked for you by Hubby, has no destination: a specification you send with neither `destination` nor `bundle_id` is refused at booking with `validation_failed` instead; cancel the booking and book again with a `destination` on each package specification); the traveller holds a Classic eSIM, which cannot take a package issued here (`customer_is_classic_holder`; the traveller moved over from the previous platform with a single-destination eSIM — contact Hubby with their `external_user_id`; the item stays `queued`, so issue it again once Hubby has corrected the eSIM); no eSIM is free to assign to a traveller with no Hubby eSIM yet (`no_free_esim_available`; retry later); or no bundle answers the package specification of the item any more (`no_bundle_resolved`; cancel the booking or book again). The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many issue calls from your account. Retry after the `Retry-After` header. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "The network provider failed to create the package, or another issue for the same traveller is still running. Nothing was issued and the `Idempotency-Key` was not stored: retry with the same `Idempotency-Key`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer (`legacy_booking_unavailable`). Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "partner_api.v1.bookings.items.package.show",
        "description": "Same body as the issue call, with the current state and timestamps. `{booking}` accepts the same identifiers as `GET /v1/bookings/{id}`; `{item}` is the item's `id`.",
        "summary": "Read the package issued for a booking item",
        "tags": [
          "BookingItemPackage"
        ],
        "parameters": [
          {
            "name": "booking",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "item",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IssuedPackage"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking or the item does not exist or is not yours, or the item has no package yet. `code`: `not_found`, `item_has_no_package`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found",
                        "item_has_no_package"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer (`legacy_booking_unavailable`). Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "partner_api.v1.bookings.items.package.destroy",
        "description": "Ends, at once on the network, a package you issued that has not activated yet. Use it for a withdrawal, a changed plan, or a traveller who never received the install material. The item returns to `queued`, so you can issue it again or cancel the booking. Nothing is charged for a terminated package, and the traveller keeps their Hubby eSIM: a new issue puts the new package on the same eSIM. A `package.terminated` webhook follows.\n\nOnce the package has activated on the network it is consumed and cannot be terminated. A package the traveller redeemed in the Hubby app cannot be terminated here. `{booking}` accepts the same identifiers as `GET /v1/bookings/{id}`; `{item}` is the item's `id`.",
        "summary": "Terminate the package issued for a booking item",
        "tags": [
          "BookingItemPackage"
        ],
        "parameters": [
          {
            "name": "booking",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "item",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "description": "Why you terminate the package: `changed_plans`, `installation_failed`, `not_received` or `other`. Optional; it is shown to Hubby support and sent back on the `package.terminated` webhook.",
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/PartnerTerminateReason"
                },
                {
                  "type": "null"
                }
              ]
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No content",
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The booking or the item does not exist or is not yours, or the item has no package. `code`: `not_found`, `item_has_no_package`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found",
                        "item_has_no_package"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "The package has activated on the network or shows use (`package_activated`), was not issued over this API (`package_not_issued_over_api`), or has already ended (`package_not_terminable`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The `reason` is not one of the listed values. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "validation_failed"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "502": {
            "description": "The network provider refused the termination; nothing changed (`package_termination_refused`). Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The booking is not on this platform yet and the previous backend, which was asked for it, could not answer (`legacy_booking_unavailable`). Retry later; the booking may exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/destinations/{destination}/bundles": {
      "get": {
        "operationId": "partner_api.v1.destinations.bundles.index",
        "description": "The bundles of one destination, in the same shape and with the same `ETag` behaviour as `GET /v1/bundles`. Only sellable bundles: active, visible and with a retail price on your price list. `prices.net` can still be `null` on one of them (see `Bundle.prices.net`).",
        "summary": "List the bundles you sell for one destination",
        "tags": [
          "Bundle"
        ],
        "parameters": [
          {
            "name": "destination",
            "in": "path",
            "required": true,
            "description": "The destination: its `id` (a ULID), an ISO 3166-1 alpha-2 or alpha-3 code, a region's code, or its English name, in any letter case. A translated name is not accepted here. Only a destination you can sell resolves: when your account has a destination list, a destination outside it answers 404 `not_found`, like an unknown value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "The `ETag` of the catalog you hold. When the catalog has not changed since, the answer is `304` with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of `Bundle`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Bundle"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "ETag": {
                "description": "A weak validator for this exact body. It changes whenever any bundle or price in the answer changes. Send it back as `If-None-Match`.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "The catalog has not changed since the `ETag` sent as `If-None-Match`. No body: keep the copy you hold.",
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The destination does not exist or is not sold. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bundles": {
      "get": {
        "operationId": "partner_api.v1.bundles.index",
        "description": "Every bundle of every destination you may sell, with both prices. Only sellable bundles: active, visible and with a retail price on your price list. `prices.net` can still be `null` on one of them (see `Bundle.prices.net`). When your account has a destination list, only the bundles of the destinations on it, the same set your bookings accept. Sync it once a day and send the `ETag` back as `If-None-Match`: an unchanged catalog answers `304` with no body.",
        "summary": "List every bundle you sell, with your prices",
        "tags": [
          "Bundle"
        ],
        "parameters": [
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "The `ETag` of the catalog you hold. When the catalog has not changed since, the answer is `304` with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of `Bundle`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Bundle"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "ETag": {
                "description": "A weak validator for this exact body. It changes whenever any bundle or price in the answer changes. Send it back as `If-None-Match`.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "The catalog has not changed since the `ETag` sent as `If-None-Match`. No body: keep the copy you hold.",
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "partner_api.v1.me",
        "description": "Read it before you create bookings: two of its settings change what a package specification may carry. When `package_strategy` is set, it fixes `package_type`, `size` and `package_duration` on every specification. When `force_destinationless` is `true`, your bookings are made without a destination, and the traveller picks one when they redeem in the Hubby app; it is never `true` for a Partner App account (`end_customer_channel` `partner_app`). Under either setting, a specification that names a `bundle_id` is refused with 422: specify by dimensions instead.",
        "summary": "Return the authenticated partner",
        "tags": [
          "CurrentPartner"
        ],
        "responses": {
          "200": {
            "description": "`Partner`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Partner"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/destinations": {
      "get": {
        "operationId": "partner_api.v1.destinations",
        "description": "Every destination you can sell, as `{ countries, regions }`: an object of two lists, not wrapped in `data`. When your account has a destination list, only the destinations on it are listed, the same set your bookings accept; otherwise every active destination. Each entry carries `id` (a ULID), `name`, `type` (`country` or `region`), `iso2`, `iso3`, `iso3s` (empty on a country, the member countries' ISO alpha-3 codes on a region) and `i18n_name`. A region also carries `slug` and `country_names` (its member countries' names keyed by ISO alpha-3 code). Any of `id`, `iso2` or `iso3` is accepted as `{destination}` by `GET /v1/destinations/{destination}`, as is the name.",
        "summary": "List the destinations you can sell",
        "tags": [
          "Destination"
        ],
        "parameters": [
          {
            "name": "iso3",
            "in": "query",
            "description": "A comma-separated list of ISO 3166-1 alpha-3 codes, in any letter case (`ESP,FRA`). Only the listed destinations answer. A region matches only by its own `iso3` code (`XCA`, `XRC`, `XME`); a region whose code is in `iso2` (`EU`, `XA`, `XN`, `XS`, `OC`) never matches this filter.",
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "countries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Destination"
                      }
                    },
                    "regions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Destination"
                      }
                    }
                  },
                  "required": [
                    "countries",
                    "regions"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/destinations/{destination}": {
      "get": {
        "operationId": "partner_api.v1.destinations.show",
        "description": "One destination you can sell, with the bundles you can sell for it in `bundles[]`. A destination outside your destination list answers 404 `not_found`, like an unknown value.",
        "summary": "Read one destination, with the bundles you sell for it",
        "tags": [
          "Destination"
        ],
        "parameters": [
          {
            "name": "destination",
            "in": "path",
            "required": true,
            "description": "The destination: its `id` (a ULID), an ISO 3166-1 alpha-2 or alpha-3 code, a region's code, or its English name, in any letter case. A translated name is not accepted here. Only a destination you can sell resolves: when your account has a destination list, a destination outside it answers 404 `not_found`, like an unknown value.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`DestinationDetail`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DestinationDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Not found `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/device-compatibility": {
      "post": {
        "operationId": "partner_api.v1.device-compatibility.check",
        "description": "Looks up the brand + device name in Hubby's own Device compatibility list and answers `supported`, `unsupported` or `unknown`, with the Device note and the matched device. Only `supported` counts as compatible. The answer is about the phone model, not about the traveller's phone.",
        "summary": "Check one device",
        "tags": [
          "DeviceCompatibility"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckDeviceCompatibilityData"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`DeviceCompatibility`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DeviceCompatibility"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_in_use"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "const": "idempotency_key_reused"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "partner_api.v1.device-compatibility.index",
        "description": "Lists the Supported devices on Hubby's own Device compatibility list, ordered by brand then name, so the traveller can pick their phone. Send the picked brand and name to the check. Filter by `brand` to show one brand only. The list is not paginated.",
        "summary": "List Supported devices",
        "tags": [
          "DeviceCompatibility"
        ],
        "parameters": [
          {
            "name": "brand",
            "in": "query",
            "description": "Only the devices of this brand answer, for example `Samsung`. Case, spaces, punctuation and accents do not matter, as in the check. A brand that is not on the list answers an empty `devices` list.",
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`DeviceCompatibilityList`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DeviceCompatibilityList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/esims/{iccid}": {
      "get": {
        "operationId": "partner_api.v1.esims.show",
        "description": "One of your travellers' eSIMs with its packages and their usage, from Hubby's last readout of the network; `refreshed_at` says when that was. The plain read is cheap and may be polled. Add `refresh=true` to read the network now: that is rate-limited per Partner. Any ICCID that is not held by one of your own travellers answers `404`.",
        "summary": "Read an eSIM and its packages",
        "tags": [
          "Esim"
        ],
        "parameters": [
          {
            "name": "iccid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refresh",
            "in": "query",
            "description": "`true` reads the eSIM from the network now instead of answering the last readout. Rate-limited per Partner; `429` with `Retry-After` past the limit.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "enum": [
                "1",
                "0",
                "true",
                "false",
                null
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`Esim`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Esim"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No eSIM with this ICCID is held by one of your travellers. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "`refresh` is not `true`, `false`, `1` or `0`. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "validation_failed"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many reads with `refresh=true` from your account. Retry after the `Retry-After` header; a read without `refresh` still answers. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/esims/{iccid}/usage": {
      "get": {
        "operationId": "partner_api.v1.esims.usage",
        "description": "What the traveller can still use on this eSIM, one entry per destination: packages for the same destination add up into one entry, so two Spain top-ups are one Spain entry with their data added together. Data and unlimited packages for one destination are two entries; a country and a region that covers it are two entries. An entry exists while at least one of its packages is live; packages that ended earlier on the same trip still count in its totals. Newest first, by the most recently added live package. An empty `data` means nothing on the eSIM can still be used. Packages the traveller bought in the Hubby app count too. Answers for every ICCID that `GET /v1/esims/{iccid}` answers for, and takes `refresh` the same way, under the same rate limit. Any ICCID that is not held by one of your own travellers answers `404`.",
        "summary": "Read what a traveller can still use on an eSIM, per destination",
        "tags": [
          "Esim"
        ],
        "parameters": [
          {
            "name": "iccid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refresh",
            "in": "query",
            "description": "`true` reads the eSIM from the network now instead of answering the last readout. Rate-limited per Partner; `429` with `Retry-After` past the limit.",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "enum": [
                "1",
                "0",
                "true",
                "false",
                null
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of `DestinationUsage`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DestinationUsage"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No eSIM with this ICCID is held by one of your travellers. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "`refresh` is not `true`, `false`, `1` or `0`. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "validation_failed"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many reads with `refresh=true` from your account, counted together with `GET /v1/esims/{iccid}`. Retry after the `Retry-After` header; a read without `refresh` still answers. `code`: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/esims/{iccid}/instructions": {
      "get": {
        "operationId": "partner_api.v1.esims.instructions",
        "description": "Step-by-step installation instructions for this eSIM, per phone family, in the traveller's language, with the eSIM's own activation material and the Partner name (`name` in `GET /v1/me`) filled in. The copy is the guided setup of the Hubby web app. Copy and media are maintained by Hubby and may change between calls without a version bump; do not cache them longer than a week. Answers for every ICCID that `GET /v1/esims/{iccid}` answers for, also after you terminated its packages or cancelled their booking. The language is `locale` from the query, else the locale of your latest booking that issued a package on this eSIM (live or terminated), else your `default_locale` (`GET /v1/me`), else `en-US`. The eSIM label is the Partner name followed by ` eSIM`; a Visual identity has no effect on it. An Android family can also carry an APN setup (`apn`), for an eSIM that has signal but no internet; it is `null` on the iPhone. Any ICCID that is not held by one of your own travellers answers `404`. `503` when the instructions cannot be read right now; retry later.",
        "summary": "Read the installation instructions for an eSIM",
        "tags": [
          "Esim"
        ],
        "parameters": [
          {
            "name": "iccid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "The language of the instructions, one of the `SupportedLocale` tags such as `de-DE`; another tag is refused with 422. Default: the locale of your latest booking that issued a package on this eSIM (live or terminated), else your `default_locale` (`GET /v1/me`), else `en-US`. Copy without a translation in that language is English.",
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/SupportedLocale"
                },
                {
                  "type": "null"
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`InstallInstructions`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InstallInstructions"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No eSIM with this ICCID is held by one of your travellers. `code`: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "not_found"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "`locale` is not a supported locale tag. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "validation_failed"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "errors",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The instructions cannot be read right now (`install_instructions_unavailable`). Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/redirect-tokens": {
      "post": {
        "operationId": "partner_api.v1.redirect-tokens.create",
        "description": "A short-lived, single-use token that signs one of your travellers in to the Hubby web app, by your `external_user_id`; no booking is needed. The traveller is found, imported, or created on this call: a traveller you have not named before is created now. For a Partner that moved over from the legacy platform, Hubby first asks the legacy platform for the traveller, and imports their legacy eSIMs, packages and bookings when it holds them; only a traveller it does not hold is created fresh. While the legacy platform cannot answer, the call answers `503` with `Retry-After`, and nothing is created. The token lives `expires_in` seconds (300) and is consumed by the first sign-in.",
        "summary": "Create a redirect token that signs one of your travellers in to the Hubby web app",
        "tags": [
          "RedirectToken"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. A unique key for this request, for example a UUID, reused across every retry of it. A retry with the same key replays the stored success for 24 hours instead of running the request again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRedirectTokenData"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`RedirectToken`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/RedirectToken"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "description": "Sent as `true` only when this answer is the stored answer to an earlier request with the same `Idempotency-Key`, replayed unchanged. Absent on the first answer.",
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated `code`: `unauthenticated`, `timestamp_out_of_window`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Error overview."
                    },
                    "code": {
                      "type": "string",
                      "description": "A stable code to branch on.",
                      "enum": [
                        "unauthenticated",
                        "timestamp_out_of_window"
                      ]
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "The legacy platform holds a record for this traveller that cannot be imported (`legacy_graph_not_importable`). Retrying gives the same answer; contact Hubby. A request with the same `Idempotency-Key` is still running (`idempotency_key_in_use`): retry with the same key after `Retry-After`. If the first request succeeded, the retry gets its stored answer. If it was refused, nothing was stored and the key is free again, so the retry runs the request again.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "legacy_graph_not_importable"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_in_use"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Sent with `idempotency_key_in_use` only: seconds to wait before retrying with the same key.",
                "required": false,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "`external_user_id` is missing, not a string, or longer than 255 characters. The `Idempotency-Key` was already used for a different request (`idempotency_key_reused`): send a new key. `code`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "errors": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "enum": [
                            "validation_failed"
                          ]
                        }
                      },
                      "required": [
                        "message",
                        "errors",
                        "code"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "description": "A stable code to branch on.",
                          "const": "idempotency_key_reused"
                        }
                      },
                      "required": [
                        "message",
                        "code"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The legacy platform could not be reached to import this traveller (`bridge_unavailable`). Nothing was created. Retry after the `Retry-After` header.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "bridge_unavailable"
                    }
                  },
                  "required": [
                    "message",
                    "code"
                  ]
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              },
              "X-Hubby-Call-Id": {
                "description": "The id of this call in Hubby's records. Quote it when you ask Hubby about a call.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "partnerKey": {
        "type": "apiKey",
        "description": "Your public API key id. The API secret signs the request and is never sent.",
        "in": "header",
        "name": "X-Api-Key"
      },
      "partnerSignature": {
        "type": "apiKey",
        "description": "Hex HMAC-SHA256 over the canonical request, keyed by the API secret. Canonical form: the newline-joined string \"HUBBY-HMAC-SHA256-v1\\n{METHOD}\\n{host}\\n{request URI}\\n{timestamp}\\n{sha256(body)}\".",
        "in": "header",
        "name": "X-Signature"
      },
      "partnerTimestamp": {
        "type": "apiKey",
        "description": "Request timestamp in epoch seconds. A request is refused with `401` `timestamp_out_of_window` when its timestamp is more than 300 seconds old or more than 5 seconds ahead of Hubby's clock: keep your clock in sync. Within the window, a request is accepted on each send; Hubby does not refuse a repeated request by its timestamp.",
        "in": "header",
        "name": "X-Timestamp"
      }
    },
    "schemas": {
      "AnalyticsActivation": {
        "type": "object",
        "properties": {
          "rate": {
            "description": "Activated over claimed, as a rate.",
            "$ref": "#/components/schemas/AnalyticsRatio"
          },
          "claimed": {
            "type": "integer",
            "description": "Booking codes first claimed in the judged part of the range."
          },
          "activated": {
            "type": "integer",
            "description": "Of those, how many had their eSIM package activated within the window."
          },
          "window_days": {
            "type": "integer",
            "description": "How long after the first claim an activation still counts, in days."
          },
          "judged_range": {
            "type": [
              "object",
              "null"
            ],
            "description": "The part of the range old enough to judge, as UTC dates with both ends included; null when none of it is.",
            "properties": {
              "from": {
                "type": "string",
                "description": "First judged day (UTC)."
              },
              "to": {
                "type": "string",
                "description": "Last judged day (UTC)."
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "is_immature": {
            "type": "boolean",
            "description": "True when the whole range is too recent to judge, so no rate is given; read it as \"too early to tell\", not as a low rate."
          }
        },
        "required": [
          "rate",
          "claimed",
          "activated",
          "window_days",
          "judged_range",
          "is_immature"
        ],
        "title": "AnalyticsActivation"
      },
      "AnalyticsBreakdowns": {
        "type": "object",
        "properties": {
          "destinations": {
            "type": "object",
            "description": "Codes issued in the range, by destination. Includes codes of bookings moved over from the previous platform, and packages bought in the app with no booking, each dated by the code's own creation; so it can differ from the funnel's `codes_issued`.",
            "properties": {
              "codes": {
                "type": "integer",
                "description": "Codes issued in the range that have a destination. The `top` counts add up to it."
              },
              "top": {
                "type": "array",
                "description": "Every destination with a code issued in the range, most codes first.",
                "items": {
                  "$ref": "#/components/schemas/AnalyticsDestinationCount"
                }
              }
            },
            "required": [
              "codes",
              "top"
            ]
          },
          "bundle_sizes": {
            "type": "object",
            "description": "Codes issued in the range, by bundle size. Includes codes of bookings moved over from the previous platform, and packages bought in the app with no booking, each dated by the code's own creation; so it can differ from the funnel's `codes_issued`.",
            "properties": {
              "codes": {
                "type": "integer",
                "description": "Codes issued in the range. The `distribution` counts add up to it."
              },
              "distribution": {
                "type": "array",
                "description": "One row per bundle size, most codes first.",
                "items": {
                  "$ref": "#/components/schemas/AnalyticsBundleSizeCount"
                }
              }
            },
            "required": [
              "codes",
              "distribution"
            ]
          },
          "discounts": {
            "type": "object",
            "description": "Discounts given on payments that went through in the range.",
            "properties": {
              "discounted_payments": {
                "type": "integer",
                "description": "Payments in the range that carried a discount of any kind: a discount code, a limited-time offer or an upgrade offer."
              },
              "by_code": {
                "type": "array",
                "description": "The discount given through each discount code, per currency. A discount from an offer has no code, so it is counted in `discounted_payments` only.",
                "items": {
                  "$ref": "#/components/schemas/AnalyticsDiscountCode"
                }
              }
            },
            "required": [
              "discounted_payments",
              "by_code"
            ]
          }
        },
        "required": [
          "destinations",
          "bundle_sizes",
          "discounts"
        ],
        "title": "AnalyticsBreakdowns"
      },
      "AnalyticsBundleSizeCount": {
        "type": "object",
        "properties": {
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The bundle's data allowance in bytes. Null for an unlimited bundle."
          },
          "label": {
            "type": "string",
            "description": "The size as a reader would say it, in binary units (`5GB` is 5 × 1024³ bytes), or `Unlimited`."
          },
          "count": {
            "type": "integer",
            "description": "Codes issued in the range with this bundle size."
          }
        },
        "required": [
          "size_bytes",
          "label",
          "count"
        ],
        "title": "AnalyticsBundleSizeCount"
      },
      "AnalyticsDestinationCount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The destination's id. A destination withdrawn since keeps its row and its name."
          },
          "name": {
            "type": "string",
            "description": "The destination's name."
          },
          "count": {
            "type": "integer",
            "description": "Codes issued in the range for this destination."
          }
        },
        "required": [
          "id",
          "name",
          "count"
        ],
        "title": "AnalyticsDestinationCount"
      },
      "AnalyticsDiscountCode": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "The discount code as travellers enter it."
          },
          "discount": {
            "type": "object",
            "description": "The discount given through this code in the range, per ISO 4217 currency code, in integer minor units. Currencies are never converted or added together.",
            "additionalProperties": {
              "type": "integer"
            }
          }
        },
        "required": [
          "code",
          "discount"
        ],
        "title": "AnalyticsDiscountCode"
      },
      "AnalyticsFunnel": {
        "type": "object",
        "properties": {
          "population": {
            "type": "string",
            "description": "Always `new`: the funnel covers bookings made on this platform only. Bookings moved over from the previous platform are left out, so `cohort.codes` can differ from the summary's `bookings.total`, which includes them."
          },
          "cohort": {
            "type": "object",
            "description": "The codes the funnel follows.",
            "properties": {
              "codes": {
                "type": "integer",
                "description": "Booking codes (one per traveller) of the new bookings created in the range: the funnel's first step."
              },
              "bookings": {
                "type": "integer",
                "description": "The new bookings those codes belong to."
              },
              "moved_over_codes_excluded": {
                "type": "integer",
                "description": "Codes of bookings moved over from the previous platform, left out of the funnel."
              },
              "test_codes_excluded": {
                "type": "integer",
                "description": "Codes of test bookings, left out of the funnel."
              }
            },
            "required": [
              "codes",
              "bookings",
              "moved_over_codes_excluded",
              "test_codes_excluded"
            ]
          },
          "is_immature": {
            "type": "boolean",
            "description": "True when the range ended within the last 14 days and has codes: travellers are still claiming, installing and activating, so later steps are still climbing. Read low rates as \"too early to tell\"."
          },
          "steps": {
            "type": "array",
            "description": "The steps, in order: `codes_issued`, `claimed`, `paid`, `esim_installed`, `package_activated`, `used_data`. Empty when no new booking was created in the range.",
            "items": {
              "$ref": "#/components/schemas/AnalyticsFunnelStep"
            }
          },
          "dimension": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `dimension` the funnel is broken down by, as requested; null when none was."
          },
          "breakdown": {
            "type": [
              "array",
              "null"
            ],
            "description": "The funnel split by `dimension`, one group per value, whose codes and step counts add up to the funnel above. Null when no `dimension` was requested or no new booking was created in the range.",
            "items": {
              "$ref": "#/components/schemas/AnalyticsFunnelGroup"
            }
          }
        },
        "required": [
          "population",
          "cohort",
          "is_immature",
          "steps",
          "dimension",
          "breakdown"
        ],
        "title": "AnalyticsFunnel"
      },
      "AnalyticsFunnelGroup": {
        "type": "object",
        "properties": {
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "The dimension's value: a destination id, a bundle size in bytes, a sales channel, a booking source or a transaction type. Null for the codes that have none: a code with no destination has no destination, an unlimited bundle has no size, and a code not sold in the app has no sales channel or transaction type."
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "A readable name for the value: the destination's name or the bundle size (for example \"5GB\"), otherwise the value itself. Null when the value is null."
          },
          "codes": {
            "type": "integer",
            "description": "This group's part of `cohort.codes`."
          },
          "steps": {
            "type": "array",
            "description": "This group's funnel, with the same steps as the whole funnel.",
            "items": {
              "$ref": "#/components/schemas/AnalyticsFunnelStep"
            }
          }
        },
        "required": [
          "value",
          "label",
          "codes",
          "steps"
        ],
        "title": "AnalyticsFunnelGroup"
      },
      "AnalyticsFunnelStep": {
        "type": "object",
        "properties": {
          "step": {
            "type": "string",
            "description": "Which step: `codes_issued` (every booking code of the bookings created in the range), `claimed` (the traveller claimed the code), `paid` (the code was paid for in the app; only codes sold in the app can be), `esim_installed` (the eSIM carrying the code's package was installed), `package_activated` (the package went live on the network, at any time after the booking was created, with no 14-day window) or `used_data` (the package carried its first byte of data). Always in this order."
          },
          "status": {
            "type": "string",
            "description": "`recorded` when the step is counted, also with a count of 0; `not_recorded` only for `claimed`, when no code in the funnel has a claim record, so it cannot be counted and is skipped as the previous step of the next one; `not_applicable` when it cannot happen to any code in the funnel (`paid`, when no code was sold in the app)."
          },
          "count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many codes reached this step, whenever that happened. For `paid`, out of the codes sold in the app only. Null when the step is not recorded or not applicable."
          },
          "rate": {
            "description": "This step's count over the previous recorded step's count; for `paid`, over the codes sold in the app, and `paid` is never the previous step of another. Null for `codes_issued` and for a step that is not recorded or not applicable.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/AnalyticsRatio"
              },
              {
                "type": "null"
              }
            ]
          },
          "share_of_codes": {
            "description": "This step's count over `codes_issued`. Null for `codes_issued`, for `paid` and for a step that is not recorded or not applicable.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/AnalyticsRatio"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "step",
          "status",
          "count",
          "rate",
          "share_of_codes"
        ],
        "title": "AnalyticsFunnelStep"
      },
      "AnalyticsMeta": {
        "type": "object",
        "properties": {
          "range": {
            "type": "object",
            "description": "The range the figures cover, as UTC dates with both ends included.",
            "properties": {
              "from": {
                "type": "string",
                "description": "First day (UTC)."
              },
              "to": {
                "type": "string",
                "description": "Last day (UTC)."
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "timezone": {
            "type": "string",
            "description": "Days are UTC days, always: a day runs from 00:00:00 to 23:59:59 UTC.",
            "const": "UTC"
          },
          "computed_at": {
            "type": "string",
            "format": "date-time",
            "description": "When these figures were computed. A repeated request within 15 minutes is answered from the same computation, so this can be up to 15 minutes ago."
          },
          "partner_ids": {
            "type": "array",
            "description": "The Partners whose figures are included: your own (unless `include_self=0`) and, with key inheritance, your descendants that are included.",
            "items": {
              "type": "string"
            }
          },
          "excluded_partners": {
            "type": "array",
            "description": "Descendants left out of every figure because they have not moved to this platform yet, with their names. Empty for a key without key inheritance.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name"
              ]
            }
          },
          "cut_over_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present when the range starts before your own Partner moved to this platform: activity before that day is on the previous platform and is not in these figures. Null otherwise, also with `include_self=0`; the note is never about descendants (their earlier activity is left out without a note)."
          }
        },
        "required": [
          "range",
          "timezone",
          "computed_at",
          "partner_ids",
          "excluded_partners",
          "cut_over_note"
        ],
        "title": "AnalyticsMeta"
      },
      "AnalyticsPartnerContribution": {
        "type": "object",
        "properties": {
          "partner_id": {
            "type": "string",
            "description": "The Partner these figures are for, one of `meta.partner_ids`."
          },
          "name": {
            "type": "string",
            "description": "The Partner's name, to label the row with."
          },
          "bookings": {
            "type": "integer",
            "description": "This Partner's part of `bookings.total`."
          },
          "customers": {
            "type": "integer",
            "description": "This Partner's part of `customers.total`."
          },
          "revenue_net": {
            "type": "object",
            "description": "This Partner's part of `revenue_net`, per ISO 4217 currency code, in integer minor units. Empty when there was none.",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "data_consumed_bytes": {
            "type": "integer",
            "description": "This Partner's part of `data_consumed_bytes`, in bytes."
          }
        },
        "required": [
          "partner_id",
          "name",
          "bookings",
          "customers",
          "revenue_net",
          "data_consumed_bytes"
        ],
        "title": "AnalyticsPartnerContribution"
      },
      "AnalyticsRatio": {
        "type": "object",
        "properties": {
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "The rate as a percentage from 0 to 100, rounded to two decimals; null when suppressed."
          },
          "suppressed": {
            "type": "boolean",
            "description": "True when the denominator is below 30 records, too few for the rate to mean anything."
          },
          "numerator": {
            "type": "integer",
            "description": "How many records met the condition."
          },
          "denominator": {
            "type": "integer",
            "description": "How many records the rate is out of."
          }
        },
        "required": [
          "value",
          "suppressed",
          "numerator",
          "denominator"
        ],
        "title": "AnalyticsRatio"
      },
      "AnalyticsSummary": {
        "type": "object",
        "properties": {
          "bookings": {
            "type": "object",
            "description": "Bookings created in the range, test bookings left out. Includes bookings moved over from the previous platform, created here on the day they moved.",
            "properties": {
              "total": {
                "type": "integer",
                "description": "New plus moved over."
              },
              "new": {
                "type": "integer",
                "description": "Bookings made on this platform."
              },
              "moved_over": {
                "type": "integer",
                "description": "Bookings moved over from the previous platform."
              },
              "tests_excluded": {
                "type": "integer",
                "description": "Test bookings created in the range and left out of every figure."
              }
            },
            "required": [
              "total",
              "new",
              "moved_over",
              "tests_excluded"
            ]
          },
          "customers": {
            "type": "object",
            "description": "Customers (travellers) gained in the range.",
            "properties": {
              "total": {
                "type": "integer",
                "description": "New plus moved over."
              },
              "new": {
                "type": "integer",
                "description": "Customers who first appeared on this platform."
              },
              "moved_over": {
                "type": "integer",
                "description": "Customers moved over from the previous platform."
              }
            },
            "required": [
              "total",
              "new",
              "moved_over"
            ]
          },
          "departing_from_today": {
            "type": "object",
            "description": "Bookings departing from today onwards, whatever the range.",
            "properties": {
              "next_30d": {
                "type": "integer",
                "description": "Departing between today and 30 days from today (UTC)."
              },
              "next_60d": {
                "type": "integer",
                "description": "Departing between today and 60 days from today (UTC)."
              },
              "next_90d": {
                "type": "integer",
                "description": "Departing between today and 90 days from today (UTC)."
              }
            },
            "required": [
              "next_30d",
              "next_60d",
              "next_90d"
            ]
          },
          "activation": {
            "description": "The 14-day claim-to-activation rate for codes claimed in the judged part of the range (`judged_range`): only claims at least 14 days old are judged.",
            "$ref": "#/components/schemas/AnalyticsActivation"
          },
          "revenue_net": {
            "type": "object",
            "description": "Net revenue in the range: purchases minus refunds, per ISO 4217 currency code, in integer minor units. Currencies are never converted or added together. Empty when there was none.",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "data_consumed_bytes": {
            "type": "integer",
            "description": "Mobile data your travellers used in the range on packages sold through this platform, in bytes."
          },
          "used_data_packages": {
            "type": "integer",
            "description": "Packages sold through this platform that started using data in the range."
          },
          "by_partner": {
            "type": "array",
            "description": "Each Partner in `meta.partner_ids`, with its part of bookings, customers, net revenue and data consumed. The rows add up to the totals above. One row when the figures are for your own Partner alone.",
            "items": {
              "$ref": "#/components/schemas/AnalyticsPartnerContribution"
            }
          },
          "comparison": {
            "type": [
              "object",
              "null"
            ],
            "description": "The same headline figures for the preceding period of equal length, when `compare` was set. Null otherwise.",
            "properties": {
              "range": {
                "type": "object",
                "description": "The preceding period, as UTC dates with both ends included.",
                "properties": {
                  "from": {
                    "type": "string",
                    "description": "First day (UTC)."
                  },
                  "to": {
                    "type": "string",
                    "description": "Last day (UTC)."
                  }
                },
                "required": [
                  "from",
                  "to"
                ]
              },
              "bookings": {
                "type": "integer",
                "description": "Bookings created in the preceding period, counted as `bookings.total` is."
              },
              "revenue_net": {
                "type": "object",
                "description": "Net revenue in the preceding period, per currency, in integer minor units.",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "data_consumed_bytes": {
                "type": "integer",
                "description": "Data used in the preceding period on packages sold through this platform, in bytes."
              },
              "activation": {
                "description": "The activation rate for codes claimed in the preceding period.",
                "$ref": "#/components/schemas/AnalyticsActivation"
              },
              "used_data_packages": {
                "type": "integer",
                "description": "Packages that started using data in the preceding period."
              }
            },
            "required": [
              "range",
              "bookings",
              "revenue_net",
              "data_consumed_bytes",
              "activation",
              "used_data_packages"
            ]
          }
        },
        "required": [
          "bookings",
          "customers",
          "departing_from_today",
          "activation",
          "revenue_net",
          "data_consumed_bytes",
          "used_data_packages",
          "by_partner",
          "comparison"
        ],
        "title": "AnalyticsSummary"
      },
      "AnalyticsTimeseries": {
        "type": "object",
        "properties": {
          "bucket": {
            "type": "string",
            "description": "The bucket every row covers: `day`, `week` (ISO weeks, Monday to Sunday) or `month`."
          },
          "series": {
            "type": "array",
            "description": "One row per bucket, oldest first, from the bucket holding `meta.range.from` to the bucket holding `meta.range.to`.",
            "items": {
              "$ref": "#/components/schemas/AnalyticsTimeseriesBucket"
            }
          }
        },
        "required": [
          "bucket",
          "series"
        ],
        "title": "AnalyticsTimeseries"
      },
      "AnalyticsTimeseriesBucket": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "The bucket's first day (UTC): the day itself, the Monday of the week, or the first of the month. A week or month bucket at either end of the range can start before or end after it; only the days inside the range are counted."
          },
          "bookings_created": {
            "type": "integer",
            "description": "Bookings created in the bucket, test bookings left out. Includes bookings moved over from the previous platform, created here on the day they moved."
          },
          "bookings_departing": {
            "type": "integer",
            "description": "Bookings whose departure date falls in the bucket, test bookings left out, moved-over bookings included. This follows the requested range, unlike the summary's `departing_from_today`, which always looks forward from today."
          },
          "bookings_cancelled": {
            "type": "integer",
            "description": "Bookings cancelled in the bucket, test bookings left out, moved-over bookings included."
          },
          "customers": {
            "type": "integer",
            "description": "Customers (travellers) gained in the bucket."
          },
          "revenue_net": {
            "type": "object",
            "description": "Net revenue in the bucket: purchases minus refunds, per ISO 4217 currency code, in integer minor units. Currencies are never converted or added together. Empty when there was none.",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "data_consumed_bytes": {
            "type": "integer",
            "description": "Mobile data your travellers used in the bucket on packages sold through this platform, in bytes."
          }
        },
        "required": [
          "date",
          "bookings_created",
          "bookings_departing",
          "bookings_cancelled",
          "customers",
          "revenue_net",
          "data_consumed_bytes"
        ],
        "title": "AnalyticsTimeseriesBucket"
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "partner_id": {
            "type": "string"
          },
          "visual_identity_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "external_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "external_reference_secondary": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "$ref": "#/components/schemas/BookingSource"
          },
          "depart_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "return_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "locale": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "gender": {
            "type": [
              "string",
              "null"
            ]
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ]
          },
          "pax": {
            "type": [
              "integer",
              "null"
            ]
          },
          "flight_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "departure_location": {
            "type": [
              "string",
              "null"
            ]
          },
          "communication_options": {
            "type": [
              "object",
              "null"
            ],
            "description": "The `communication_options` you sent. Hubby stores and returns it; it does not control Hubby's messages. Hubby emails a traveller only when Hubby has turned on emails for your account, by agreement.",
            "properties": {
              "should_send_message": {
                "type": "boolean"
              },
              "channels": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "is_cancellable": {
            "type": "boolean",
            "description": "Whether `DELETE /v1/bookings/{id}` would cancel this booking now. False once it is cancelled, after `depart_at`, while any item is issued, or while a traveller's money sits on an item. Read it before offering a cancel button."
          },
          "is_pseudonymized": {
            "type": "boolean"
          },
          "items": {
            "type": "array",
            "description": "The booking's items, one per package specification. Each item's `id` is the `{item}` you issue, read and terminate its package with.",
            "items": {
              "$ref": "#/components/schemas/BookingItem"
            }
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "partner_id",
          "visual_identity_id",
          "external_reference",
          "external_reference_secondary",
          "source",
          "depart_at",
          "return_at",
          "locale",
          "email",
          "phone",
          "first_name",
          "last_name",
          "full_name",
          "title",
          "gender",
          "date_of_birth",
          "pax",
          "flight_number",
          "departure_location",
          "communication_options",
          "cancelled_at",
          "is_cancellable",
          "is_pseudonymized",
          "items",
          "created_at",
          "updated_at"
        ],
        "title": "Booking"
      },
      "BookingItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": [
              "string",
              "null"
            ],
            "description": "The human-typeable redemption code for this package, twelve\ncharacters from an unambiguous alphabet. It is a bearer\ncredential: whoever redeems it is bound to the package, so treat\nit like a voucher and do not put it anywhere public.\nAbsent when your End-customer channel is `partner_app`."
          },
          "offer_uuid": {
            "type": [
              "string",
              "null"
            ],
            "description": "The key of this item's upgrade-offer page, where the traveller\ncan buy a larger package before redeeming. Distinct from `id`:\n`id` is what the outbound webhooks send as `package_queue_uuid`,\nthis is the page's own token, and the two are never\ninterchangeable. Use it only if you build the link yourself —\n`upgrade_url` is already the whole link.\nAbsent when your End-customer channel is `partner_app`."
          },
          "upgrade_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The traveller's upgrade-offer link for this item, ready to send.\nIt is a bearer capability URL — whoever opens it can buy an\nupgrade for this package — so treat it like `code` and keep it\nout of anywhere public. Null only on an environment with no\npublic-pages deployment configured.\nAbsent when your End-customer channel is `partner_app`."
          },
          "deep_link_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A universal link that opens the Hubby app and applies this\nitem's `code` automatically, so a traveller never types it. On\na device without the app it leads to the store instead; on\ndesktop, to a download page — it is a mobile CTA, not a general\nredemption URL. Send it as given rather than rebuilding it.\nNull for an item carrying no code.\nAbsent when your End-customer channel is `partner_app`."
          },
          "external_user_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "destination": {
            "type": [
              "string",
              "null"
            ],
            "description": "The country's ISO 3166-1 alpha-2 code, or the region's code (for example `EU`). List the region codes from `GET /v1/destinations`."
          },
          "package_type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PackageType"
              },
              {
                "type": "null"
              }
            ]
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The size you asked for, in bytes: the `size` of the specification, or the bundle's size when you named a `bundle_id`. Not always the size issued: Hubby picks the closest bundle of the same type at issue, so read the issued size from the bundle `package.bundle_id` names. Null for a specification with no size."
          },
          "duration_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "start_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "end_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "description": "Deprecated: read `state` instead. `state` covers the whole lifecycle of the item, including the package issued from it.",
            "$ref": "#/components/schemas/PackageQueueStatus"
          },
          "bundle_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The bundle this item books: the one named by `bundle_id` at booking, or, once issued, the bundle the closest match chose. Null while an item specified by dimensions is not issued yet."
          },
          "state": {
            "description": "Where this item is in its lifecycle. Read it instead of `status`: `queued` (reserved, not issued yet; you can issue it), `unavailable` (cannot be issued now: no bundle answers its specification, or a payment is still pending; cancel the booking or book again), `redeemed` (issued, install material available; with `package: null`, already spent, or brought over spent from the previous backend, so there is nothing to install), `installed` (the eSIM is on a phone), `activated` (live on the network, validity running), `expired` (the validity elapsed, or the provider ended the package after it activated; a package that used up its data stays `activated` until the provider ends it), `terminated` (ended before activation; on an item this is rare: when a package you issued ends before activation, the item returns to `queued` with `package: null`, so an item reads `terminated` only when the package showed data use before Hubby recorded its activation, or when the traveller redeemed the item in the Hubby app; treat it like `expired`), `cancelled` (the booking was cancelled, or the item's payment was rolled back).",
            "$ref": "#/components/schemas/PackageLifecycleState"
          },
          "package": {
            "description": "The package issued for this item, the same body `GET …/items/{item}/package` answers. Null until it is issued.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/IssuedPackage"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "customer_id",
          "package_type",
          "size_bytes",
          "duration_seconds",
          "start_at",
          "end_at",
          "status",
          "bundle_id",
          "state",
          "package"
        ],
        "title": "BookingItem"
      },
      "BookingSource": {
        "type": "string",
        "enum": [
          "api",
          "platform",
          "manual",
          "synthetic",
          "migrated",
          "free_esim",
          "import",
          "membership",
          "store"
        ],
        "title": "BookingSource"
      },
      "Bundle": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The bundle's id. Stable: name it as `bundle_id` to book exactly this bundle."
          },
          "destination": {
            "description": "The destination the bundle is sold for. A region carries its code in `iso2` or in `iso3`.",
            "$ref": "#/components/schemas/DestinationSummary"
          },
          "type": {
            "description": "The package type: `data_limited`, `starter` or `unlimited`. Send it back as `package_type` when you specify by dimensions.",
            "$ref": "#/components/schemas/PackageType"
          },
          "label": {
            "type": "string",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "provider": {
            "type": "string",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The validity in whole days, counted from first use: `duration_seconds` / 86400, rounded to the nearest whole day. Unlimited bundles are listed once for every day count that has a price."
          },
          "package_template": {
            "type": "object",
            "description": "Internal; not for Partner use; do not build on it.",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "description": "Internal; not for Partner use; do not build on it.",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "traffic_policy": {
            "type": [
              "object",
              "null"
            ],
            "description": "Internal; not for Partner use; do not build on it.",
            "properties": {
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "description"
            ]
          },
          "fair_use_throttle": {
            "description": "The Fair-use throttle of an unlimited bundle: after `throttle_after_bytes` in one day the speed drops to `throttled_kbps` until the day resets. Show it to the traveller before purchase. Null on a bundle that is not unlimited.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/FairUseThrottle"
              },
              {
                "type": "null"
              }
            ]
          },
          "is_active": {
            "type": "boolean",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "is_visible": {
            "type": "boolean",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "priority": {
            "type": "integer",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When anything on the bundle last changed. A price change does not move it: compare `prices` too, or watch the catalog `ETag`."
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal; not for Partner use; do not build on it."
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal; not for Partner use; do not build on it."
          },
          "pricing_info": {
            "type": "object",
            "description": "The recommended retail price in EUR major units. Kept for older integrations; read `prices` instead.",
            "properties": {
              "final_price": {
                "type": "number"
              },
              "original_price": {
                "type": "number"
              },
              "currency": {
                "type": "string"
              },
              "currency_symbol": {
                "type": "string"
              },
              "discount_applied": {
                "type": "boolean"
              },
              "discount_percentage": {
                "type": "integer"
              }
            },
            "required": [
              "final_price",
              "original_price",
              "currency",
              "currency_symbol",
              "discount_applied",
              "discount_percentage"
            ]
          },
          "prices": {
            "type": "object",
            "description": "Both prices of the bundle, in EUR cents.",
            "properties": {
              "retail": {
                "type": "object",
                "description": "The recommended retail price: what a traveller pays in Hubby's own shop.",
                "properties": {
                  "amount": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string",
                    "const": "EUR"
                  }
                },
                "required": [
                  "amount",
                  "currency"
                ]
              },
              "net": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Your price under your agreement: the amount Hubby invoices you. Null when your price list gives the bundle a retail price but no net price. The bundle is still listed, and you can still book it: your agreement with Hubby, not the API, sets what you pay for it.",
                "properties": {
                  "amount": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string",
                    "const": "EUR"
                  }
                },
                "required": [
                  "amount",
                  "currency"
                ]
              }
            },
            "required": [
              "retail",
              "net"
            ]
          }
        },
        "required": [
          "id",
          "destination",
          "type",
          "label",
          "provider",
          "size_bytes",
          "duration_seconds",
          "duration_days",
          "package_template",
          "partner",
          "traffic_policy",
          "fair_use_throttle",
          "is_active",
          "is_visible",
          "priority",
          "created_at",
          "updated_at",
          "deleted_at",
          "created_by",
          "updated_by",
          "pricing_info",
          "prices"
        ],
        "title": "Bundle"
      },
      "CheckDeviceCompatibilityData": {
        "type": "object",
        "description": "The device to check: a brand and a device name, both required. They are\nmatched against Hubby's Device compatibility list on brand + name only; a\nvalue that is not on the list is not refused, it answers `unknown`.",
        "properties": {
          "phone_brand": {
            "type": "string",
            "description": "The phone's brand, for example `Apple` or `Samsung`. Case, spaces and punctuation do not matter."
          },
          "phone_model": {
            "type": "string",
            "description": "The device name, for example `iPhone 15 Pro` or `Galaxy S24+`, as the traveller picks it from the list. Not a model code: a model code answers `unknown`. Case, spaces and punctuation do not matter, but a variant such as `5G` is a different device."
          }
        },
        "required": [
          "phone_brand",
          "phone_model"
        ],
        "title": "CheckDeviceCompatibilityData"
      },
      "CommunicationChannel": {
        "type": "string",
        "description": "A channel you may name in a booking's `communication_options`. Hubby stores and returns it; it does not control Hubby's messages.\n",
        "enum": [
          "EMAIL",
          "WHATSAPP",
          "PUSH_NOTIFICATION",
          "SMS"
        ],
        "title": "CommunicationChannel"
      },
      "CreateBookingRequest": {
        "type": "object",
        "description": "A new booking: your reference, the trip, and one package specification per eSIM to issue. Travel dates are `depart_at` and `return_at` and branding is `visual_identity_id`, the names the booking response uses; the legacy spellings `departure_date`, `return_date` and `custom_branding` are refused.",
        "properties": {
          "external_reference": {
            "type": "string",
            "description": "Your own reference for the booking. It is unique per Partner and `depart_at` among your live bookings (a cancelled booking and a booking brought over from the previous backend do not count). Sending the same `external_reference` and `depart_at` again returns the existing booking with `201` and writes nothing, so a retry is safe. After you cancel that booking, the same `external_reference` and `depart_at` create a new booking. Bookings are never merged: the same `external_reference` with another `depart_at` creates a second booking. A lookup by reference (`GET /v1/bookings/{id}`) answers the newest booking that carries it; filter the list by `external_reference` to read all of them.",
            "maxLength": 255
          },
          "external_reference_secondary": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "visual_identity_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "depart_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trip's departure day, written `DD/MM/YYYY`, `DD-MM-YYYY` or `YYYY-MM-DD` (day first; an ISO 8601 date-time such as `2026-08-01T10:00:00Z` is accepted too). Answers return it as a date-time. Part of the retry key: see `external_reference`."
          },
          "return_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trip's return day, written `DD/MM/YYYY`, `DD-MM-YYYY` or `YYYY-MM-DD` (day first; an ISO 8601 date-time is accepted too), on or after `depart_at`."
          },
          "locale": {
            "type": [
              "string",
              "null"
            ],
            "description": "The booking's language as a BCP-47 tag. `es`, `es-ES` and `es_es` are all accepted and stored as one normalised tag. Up to 10 characters: a longer value is refused with 422. A value Hubby cannot read as a language is stored as `null` with no error, and your `default_locale` (`GET /v1/me`) then fills it.",
            "maxLength": 10
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 255
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The traveller's date of birth, a day before today, written `YYYY-MM-DD`. Answers return it as `YYYY-MM-DD`."
          },
          "pax": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many travellers the booking is for. A headcount only: it creates no packages. Send one package specification per traveller.",
            "minimum": 1,
            "maximum": 2147483647
          },
          "flight_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "departure_location": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "data": {
            "type": [
              "object",
              "null"
            ],
            "description": "Your own data about the booking: any JSON object of up to 100 keys, with any values. Stored, never returned: no booking read answers it.",
            "additionalProperties": {}
          },
          "communication_options": {
            "type": [
              "object",
              "null"
            ],
            "description": "Your messaging preferences for the traveller. Hubby stores and returns it; it does not control Hubby's messages. `should_send_message` is required when the object is sent; `channels` is optional.",
            "properties": {
              "should_send_message": {
                "type": "boolean"
              },
              "channels": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "$ref": "#/components/schemas/CommunicationChannel"
                }
              }
            },
            "required": [
              "should_send_message"
            ]
          },
          "package_specifications": {
            "type": "array",
            "description": "The package specifications to fulfil, one per traveller. When `GET /v1/me` reports a `package_strategy`, `package_type` may be omitted and `size` and `package_duration` are not required by the type: the strategy overwrites all three on every specification you create, whether or not you send them. Each specification must carry `email` or `external_user_id` when `GET /v1/me` reports `end_customer_channel: webapp`. With `end_customer_channel: native_app`, both are optional and a specification naming neither is claimed by its code. With `end_customer_channel: partner_app`, booking items carry no `code`, so a specification must name its traveller by `external_user_id` before you issue it: send it here or on the issue call; for an item that names no traveller, the issue call answers `422` on `external_user_id`.",
            "items": {
              "type": "object",
              "properties": {
                "external_user_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 255
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "email",
                  "maxLength": 255
                },
                "destination": {
                  "type": "string",
                  "description": "Required unless `bundle_id` is sent. The country or region the package is for: an ISO 3166-1 alpha-2 or alpha-3 code, a region's code, or the destination's English name, in any letter case. The value must match exactly: a translated name, a typo or any other value answers 422, as does a destination outside your destination list. A specification with neither `destination` nor `bundle_id` is refused here, at booking: `422` `validation_failed` on this field, and nothing is booked. Only when `GET /v1/me` reports `force_destinationless: true` (never on a Partner App account) may it be omitted: a sent one is then ignored, the item is booked without a destination, and issuing it over this API answers `422` `destination_required`.",
                  "maxLength": 120
                },
                "package_type": {
                  "type": "string",
                  "description": "Required unless `bundle_id` is sent. `data-limited` (or `data_limited`), `starter` or `unlimited`. Both spellings are accepted; answers always spell it `data_limited`. May also be omitted when `GET /v1/me` reports a `package_strategy`, which sets it for you.",
                  "enum": [
                    "data_limited",
                    "data-limited",
                    "starter",
                    "unlimited"
                  ]
                },
                "size": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The data allowance, as a number followed by `MB` or `GB` (`500MB`, `1.5GB`). Required for `data_limited` and `starter`, unless `bundle_id` is sent or `GET /v1/me` reports a `package_strategy`.",
                  "pattern": "^\\d+(\\.\\d+)?(MB|GB)$"
                },
                "package_duration": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "The validity in whole days. Required for `unlimited` and `starter`, unless `bundle_id` is sent or `GET /v1/me` reports a `package_strategy`.",
                  "minimum": 1
                },
                "start_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The day the traveller plans to start using this package, written `DD/MM/YYYY`, `DD-MM-YYYY` or `YYYY-MM-DD` (day first; an ISO 8601 date-time is accepted too). Stored and returned on the item; it does not start the package, which starts when it is activated."
                },
                "end_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The day the traveller plans to stop using this package, written `DD/MM/YYYY`, `DD-MM-YYYY` or `YYYY-MM-DD` (day first; an ISO 8601 date-time is accepted too), after `start_at`. Stored and returned on the item; it does not end the package. After this day the item's upgrade offer is no longer shown."
                },
                "bundle_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "A bundle `id` from the catalogue (`GET /v1/bundles`). Books exactly that bundle: its destination, type, size and duration are read from it, so `destination`, `package_type`, `size` and `package_duration` may be omitted and are ignored when sent. A bundle outside your catalogue answers 422. Omit it to specify by dimensions instead: the item then keeps the size you asked for, and Hubby picks the closest bundle of the same type when the item is issued. Refused with 422 when `GET /v1/me` reports a `package_strategy`, or when your bookings are made without a destination: those settings choose the bundle for you."
                }
              }
            },
            "minItems": 1
          },
          "partner": {
            "type": [
              "string",
              "null"
            ],
            "description": "Create this booking for one of your descendant partners instead of your own account, naming it by its `slug` (the value `GET /v1/me` reports). Optional: omit it and the booking is yours, exactly as before. No setting on your API key is needed — every account may do this. The named partner must be an active partner below you in the hierarchy, at any depth rather than only a direct child, and every partner between you and it must be active too. Naming anything else — a partner that is not yours, an inactive one, or a slug that does not exist — answers 422. The whole booking is then the named partner's: its catalog and price list, its visual identities, its Package strategy, and its customers. Only the named partner's own key can read, change, cancel or issue it, and its webhooks go to that partner. Your own key answers `404` for it from then on. Key inheritance covers analytics only: no setting lets a parent read, change, cancel or issue a descendant's booking, or receive its webhooks.",
            "maxLength": 255
          }
        },
        "required": [
          "external_reference",
          "package_specifications"
        ],
        "title": "CreateBookingRequest"
      },
      "CreateRedirectTokenData": {
        "type": "object",
        "description": "The traveller to sign in, by your own id for them. Only your own travellers can be named: the Partner is read from your API key, never from the payload.",
        "properties": {
          "external_user_id": {
            "type": "string",
            "description": "Your id for the traveller, the same value you send on bookings. A traveller you have not named before is created on this call; for a Partner that moved over from the legacy platform, their legacy record is imported first.",
            "maxLength": 255
          }
        },
        "required": [
          "external_user_id"
        ],
        "title": "CreateRedirectTokenData"
      },
      "Destination": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": "string"
              },
              "iso3s": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "minItems": 0,
                "maxItems": 0
              },
              "i18n_name": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "iso2": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "iso3": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "type",
              "iso3s",
              "i18n_name",
              "iso2",
              "iso3"
            ]
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": "string"
              },
              "iso2": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "iso3": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "iso3s": {
                "type": "array",
                "items": {}
              },
              "slug": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The destination's English name in lowercase words joined by hyphens, for example `bosnia-and-herzegovina` or `europe`."
              },
              "i18n_name": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "country_names": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": [
              "id",
              "name",
              "type",
              "iso2",
              "iso3",
              "iso3s",
              "slug",
              "i18n_name",
              "country_names"
            ]
          }
        ],
        "title": "Destination"
      },
      "DestinationDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "tier": {
            "type": "null",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "iso3s": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "iso2": {
            "type": [
              "string",
              "null"
            ],
            "description": "The country's ISO 3166-1 alpha-2 code, or the region's code when the region keeps it here (for example `EU`). Null when the region keeps its code in `iso3`."
          },
          "iso3": {
            "type": [
              "string",
              "null"
            ],
            "description": "The country's ISO 3166-1 alpha-3 code, or the region's code when the region keeps it here (for example `XCA`). Null when the region keeps its code in `iso2`."
          },
          "slug": {
            "type": [
              "string",
              "null"
            ],
            "description": "The destination's English name in lowercase words joined by hyphens, for example `bosnia-and-herzegovina` or `europe`."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "i18n_name": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "is_active": {
            "type": "boolean",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "sort_order": {
            "type": "integer",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "click_count": {
            "type": "integer",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Internal; not for Partner use; do not build on it."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal; not for Partner use; do not build on it."
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal; not for Partner use; do not build on it."
          },
          "bundles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bundle"
            }
          }
        },
        "required": [
          "id",
          "type",
          "tier",
          "iso3s",
          "iso2",
          "iso3",
          "slug",
          "name",
          "i18n_name",
          "is_active",
          "sort_order",
          "click_count",
          "created_at",
          "updated_at",
          "created_by",
          "updated_by",
          "bundles"
        ],
        "title": "DestinationDetail"
      },
      "DestinationKind": {
        "type": "string",
        "enum": [
          "country",
          "region"
        ],
        "title": "DestinationKind"
      },
      "DestinationSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The destination's id: the `id` of `GET /v1/destinations/{destination}`."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The destination's English name, for example `Spain` or `Europe`."
          },
          "type": {
            "description": "`country` or `region`.",
            "$ref": "#/components/schemas/DestinationKind"
          },
          "iso2": {
            "type": [
              "string",
              "null"
            ],
            "description": "The country's ISO 3166-1 alpha-2 code, or the region's code when the region keeps it here (for example `EU`). Null when the region keeps its code in `iso3`."
          },
          "iso3": {
            "type": [
              "string",
              "null"
            ],
            "description": "The country's ISO 3166-1 alpha-3 code, or the region's code when the region keeps it here (for example `XCA`). Null when the region keeps its code in `iso2`."
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "iso2",
          "iso3"
        ],
        "title": "DestinationSummary"
      },
      "DestinationUsage": {
        "type": "object",
        "properties": {
          "destination": {
            "description": "The destination every package in this entry is for, the same object a bundle carries in `GET /v1/bundles`. A country and a region that covers it are two entries. Top up this entry with a bundle for this destination.",
            "$ref": "#/components/schemas/DestinationSummary"
          },
          "is_unlimited": {
            "type": "boolean",
            "description": "True for unlimited packages. `total_bytes` and `remaining_bytes` are then null; `used_bytes` is still set. A destination with unlimited and data packages has one entry of each."
          },
          "total_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The data allowance of every package in `package_ids` added up, in bytes, ended packages included. Always `used_bytes + remaining_bytes`. Draw a gauge from `remaining_bytes` against `total_bytes`, as the Hubby app does. Null when `is_unlimited`."
          },
          "used_bytes": {
            "type": "integer",
            "description": "The data that is no longer available, in bytes: `total_bytes - remaining_bytes`, the figure the Hubby app shows as used. Data an ended package of the same trip did not use counts as used, because it can no longer be used. On unlimited entries, where there is no total, the data the packages in `package_ids` used, added up."
          },
          "remaining_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The data the traveller can still use, in bytes: what the live packages have left, added up, and never below `0`. A provider can report a package slightly over its allowance; that overrun is taken from what the entry's other live packages have left. `0` means used up, even when `valid_until` is still ahead; a top-up adds to it. Null when `is_unlimited`."
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this entry's time runs out. The packages in one entry are for one destination, so they run one after the other: a package waits until the one before it is used up or ends. This is the end of the running package plus the whole validity of every package waiting after it. For data, it is the latest end: a package that is used up early starts the next one early, and the entry's data stops when `remaining_bytes` reaches `0`. Null while no package in this entry is running: every live package in it still waits to start, and the first one starts on first use. An entry with a running package always has a date. Null does not mean nothing was used: the entry also lists its packages that have already ended, and a package of the other type for the same destination can be running while this entry waits for it. See `remaining_seconds` for the time left."
          },
          "remaining_seconds": {
            "type": "integer",
            "description": "The time left on this entry, in seconds, the figure behind \"N days left\" in the Hubby app: the time left on the running package plus the whole validity of every package waiting after it. Set even while `valid_until` is null: then it is the validity of every package in the entry, added up. Counts down only once a package has started."
          },
          "package_ids": {
            "type": "array",
            "description": "Every package this entry adds up, oldest first, ended packages of the same trip included: the `id` of packages in `GET /v1/esims/{iccid}`. Packages the traveller bought in the Hubby app count too.",
            "items": {
              "type": "string"
            }
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When these figures were last read from the network: the oldest read among the entry's live packages. Null when one of them was never read. Send `refresh=true` to read them now."
          }
        },
        "required": [
          "destination",
          "is_unlimited",
          "total_bytes",
          "used_bytes",
          "remaining_bytes",
          "valid_until",
          "remaining_seconds",
          "package_ids",
          "synced_at"
        ],
        "title": "DestinationUsage"
      },
      "DeviceCompatibility": {
        "type": "object",
        "properties": {
          "phone_brand": {
            "type": "string",
            "description": "The brand you sent, as you sent it."
          },
          "phone_model": {
            "type": "string",
            "description": "The device name you sent, as you sent it."
          },
          "is_compatible": {
            "type": "boolean",
            "description": "`true` only when `status` is `supported`. `false` for `unsupported` and for `unknown`: a phone that Hubby has no answer for is not known to support eSIM."
          },
          "status": {
            "description": "Hubby's answer from its own Device compatibility list, matched on brand + device name only (case, spaces and punctuation do not matter; a model code does not match). `supported`: the model is on the list as eSIM-capable. `unsupported`: the model is on the list as not eSIM-capable. `unknown`: the model is not on the list, or Hubby has no answer for it. This is a claim about the phone model, not about this traveller's phone: a supported model can still be carrier-locked, or a regional variant without eSIM. Before checkout, ask the traveller to dial `*#06#`: a phone that shows an EID has eSIM. In an Android app, prefer `EuiccManager.isEnabled()` on the device itself.",
            "$ref": "#/components/schemas/DeviceCompatibilityStatus"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Device note: a free-text caveat for the model, for example \"US: eSIM only\" or \"Wi-Fi + Cellular only\". Regional limits are here. Show it to the traveller as it is. Null when the list has no note, and always null for `unknown`."
          },
          "device": {
            "description": "The device on the list that matched. Null when `status` is `unknown`.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/DeviceCompatibilityDevice"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "phone_brand",
          "phone_model",
          "is_compatible",
          "status",
          "note",
          "device"
        ],
        "title": "DeviceCompatibility"
      },
      "DeviceCompatibilityDevice": {
        "type": "object",
        "properties": {
          "brand": {
            "type": "string",
            "description": "The brand as Hubby's list spells it."
          },
          "name": {
            "type": "string",
            "description": "The device name as Hubby's list spells it. Show this name to the traveller."
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "A model code for the device, for display only. Null when the list has none. One code can name different phones, so do not use it to identify a phone."
          },
          "year": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The year the model came out. Null when the list does not know."
          }
        },
        "required": [
          "brand",
          "name",
          "model",
          "year"
        ],
        "title": "DeviceCompatibilityDevice"
      },
      "DeviceCompatibilityList": {
        "type": "object",
        "properties": {
          "devices": {
            "type": "array",
            "description": "The Supported devices on Hubby's Device compatibility list, ordered by brand then name. Supported is a claim about the phone model per Hubby's list, not about the traveller's phone. Let the traveller pick a `name` from this list, then send that name to the check (`POST /v1/device-compatibility`). Unsupported devices are never in this list.",
            "items": {
              "$ref": "#/components/schemas/SupportedDevice"
            }
          }
        },
        "required": [
          "devices"
        ],
        "title": "DeviceCompatibilityList"
      },
      "DeviceCompatibilityStatus": {
        "type": "string",
        "description": "Hubby's answer about one phone model: `supported` (on the list as eSIM-capable), `unsupported` (on the list as not eSIM-capable) or `unknown` (not on the list, or Hubby has no answer for it). Only `supported` counts as compatible.\n",
        "enum": [
          "supported",
          "unsupported",
          "unknown"
        ],
        "title": "DeviceCompatibilityStatus"
      },
      "EndCustomerChannel": {
        "type": "string",
        "description": "Where a Partner's travellers get their eSIM. `webapp`: Hubby's web app, so each package specification names the traveller. `native_app`: the Hubby app, where travellers redeem a `code`. `partner_app`: the Partner's own app over this API, which issues and installs the package itself; its booking items carry no `code`, `offer_uuid`, `upgrade_url` or `deep_link_url`. Not inherited from a parent Partner.\n",
        "enum": [
          "webapp",
          "native_app",
          "partner_app"
        ],
        "title": "EndCustomerChannel"
      },
      "Esim": {
        "type": "object",
        "properties": {
          "iccid": {
            "type": "string",
            "description": "The eSIM's ICCID."
          },
          "external_user_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own identifier for the traveller holding this eSIM."
          },
          "installed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the eSIM was first installed on a phone. Null until then."
          },
          "packages": {
            "type": "array",
            "description": "Every package on this eSIM, oldest first, ended ones included.",
            "items": {
              "$ref": "#/components/schemas/EsimPackage"
            }
          },
          "refreshed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the usage figures were last read from the network. Null when they never were. Send `refresh=true` to read them now."
          }
        },
        "required": [
          "iccid",
          "external_user_id",
          "installed_at",
          "packages",
          "refreshed_at"
        ],
        "title": "Esim"
      },
      "EsimInstallMaterial": {
        "type": "object",
        "properties": {
          "iccid": {
            "type": "string"
          },
          "installed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this eSIM was first installed on a phone, whichever package it was installed for. Set for a repeat traveller: the eSIM is already on their phone, so skip the install screen and show that the new package is ready. Null until the eSIM is installed."
          },
          "lpa": {
            "type": [
              "string",
              "null"
            ],
            "description": "The activation string. Render it as a QR code for \"install on another device\"."
          },
          "smdp_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "The SM-DP+ address from `lpa`, for manual entry in the phone's settings."
          },
          "matching_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The matching id (activation code) from `lpa`, for manual entry in the phone's settings."
          },
          "install_links": {
            "type": "object",
            "description": "One-tap install links. Open the one for the phone that will hold the eSIM; its own installer opens. Each is null when the activation string is not in the standard `LPA:1$…` form.",
            "properties": {
              "ios": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "android": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "ios",
              "android"
            ]
          }
        },
        "required": [
          "iccid",
          "installed_at",
          "lpa",
          "smdp_address",
          "matching_id",
          "install_links"
        ],
        "title": "EsimInstallMaterial"
      },
      "EsimPackage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "booking_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The booking this package was issued from: Hubby's booking `id` (the `id` of `GET /v1/bookings/{id}`), not your reference. The webhooks send your `external_reference` under the same name, so do not join the two on it. Null for a package the traveller bought in the Hubby app, and for a package redeemed with a promo code that belongs to no booking."
          },
          "state": {
            "description": "Where this package is in its lifecycle, the same `state` its booking item carries: `redeemed`, `installed`, `activated`, `expired` (the validity elapsed, or the provider ended the package after it activated; a package that used up its data stays `activated` until the provider ends it) or `terminated` (ended before activation).",
            "$ref": "#/components/schemas/PackageLifecycleState"
          },
          "bundle_id": {
            "type": "string"
          },
          "destination": {
            "description": "The destination this package is for, the same object a bundle carries in `GET /v1/bundles`. Always set, also on a package that has ended and on a package whose bundle is no longer sold.",
            "$ref": "#/components/schemas/DestinationSummary"
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the package activated and its validity started: the time the connectivity provider reported the package activated. Hubby does not observe the network attach itself. Null before."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The latest moment this package can still be activated: the deadline the connectivity provider sets when the package is issued, after which an unused package can no longer be started. It is **not** when the package's validity ends: a package with a few days of validity is typically still activatable for about a year. Use `valid_until` for when the package stops working."
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this package's validity ends and its data stops working: the moment it activated plus its time allowance. Null until the package activates, because the clock starts at activation, not at issue. Also null for a package that carries no time allowance."
          },
          "data_used_bytes": {
            "type": "integer",
            "description": "The total data used since activation, in bytes, from Hubby's last readout of the network. It is not the use of the current day, and the API gives no daily figure: on an unlimited package, do not compare it with `fair_use_throttle.throttle_after_bytes`, which applies per day."
          },
          "data_remaining_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Data left, in bytes. `0` once the package has ended (`state` is `expired` or `terminated`), whatever was unused: an ended package has no data the traveller can still use. Null on an unlimited package, or when the allowance is not known."
          }
        },
        "required": [
          "id",
          "booking_id",
          "state",
          "bundle_id",
          "destination",
          "activated_at",
          "expires_at",
          "valid_until",
          "data_used_bytes",
          "data_remaining_bytes"
        ],
        "title": "EsimPackage"
      },
      "FairUseThrottle": {
        "type": "object",
        "properties": {
          "throttle_after_bytes": {
            "type": "integer",
            "description": "The daily threshold, in bytes: the data a traveller can use in one day at full speed. The threshold applies per day, not to the whole package."
          },
          "throttled_kbps": {
            "type": "integer",
            "description": "The speed after the daily threshold is passed, in kbps (decimal: 1 Mbps is 1000 kbps). It applies until the threshold resets."
          },
          "resets": {
            "description": "When the threshold resets and the full speed comes back.\n| |\n|---|\n| `daily` <br/> Each day: the threshold applies to the data used in one day. |",
            "$ref": "#/components/schemas/FairUseThrottleReset"
          }
        },
        "required": [
          "throttle_after_bytes",
          "throttled_kbps",
          "resets"
        ],
        "title": "FairUseThrottle"
      },
      "FairUseThrottleReset": {
        "type": "string",
        "description": "When the Fair-use throttle's threshold starts again: after it, the data used counts from zero and the full speed comes back.\n| |\n|---|\n| `daily` <br/> Each day: the threshold applies to the data used in one day. |",
        "enum": [
          "daily"
        ],
        "title": "FairUseThrottleReset"
      },
      "InstallInstructionApnSetup": {
        "type": "object",
        "properties": {
          "apn": {
            "type": "string",
            "description": "The access point name to type. The steps already name it; your app can also show it with a copy button.",
            "examples": [
              "globaldata"
            ]
          },
          "title": {
            "type": "string",
            "description": "The section's title, in the response `locale` where translated, else English."
          },
          "clip_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short screen recording of the APN setup on this phone family, in this language. Null when none exists."
          },
          "steps": {
            "type": "array",
            "description": "The steps, in order.",
            "items": {
              "$ref": "#/components/schemas/InstallInstructionStep"
            }
          }
        },
        "required": [
          "apn",
          "title",
          "clip_url",
          "steps"
        ],
        "title": "InstallInstructionApnSetup"
      },
      "InstallInstructionSection": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the section does: `rename` the line, `turn_on` the line, enable `roaming`, pick the line for `mobile_data`. Each id comes once per family.",
            "examples": [
              "rename"
            ]
          },
          "title": {
            "type": "string",
            "description": "The section's title, in the response `locale` where translated, else English."
          },
          "clip_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short screen recording of the whole section on this phone family, in this language. Null when none exists."
          },
          "steps": {
            "type": "array",
            "description": "The steps, in order.",
            "items": {
              "$ref": "#/components/schemas/InstallInstructionStep"
            }
          }
        },
        "required": [
          "id",
          "title",
          "clip_url",
          "steps"
        ],
        "title": "InstallInstructionSection"
      },
      "InstallInstructionSet": {
        "type": "object",
        "properties": {
          "family": {
            "type": "string",
            "description": "The phone family: `iphone` on iOS; `pixel`, `samsung` or `xiaomi` on Android. For an Android phone you cannot place, use `samsung`. Find a family by this value, not by its position in the list."
          },
          "one_tap": {
            "type": "object",
            "description": "Install on the phone itself: open `install_link` on that phone, then follow the steps the OS asks. On a manual-only family (Xiaomi today) there is no link, and the steps are the manual steps.",
            "properties": {
              "title": {
                "type": "string"
              },
              "install_link": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The OS's own one-tap install link for this eSIM. Null for a manual-only family (Xiaomi today): that phone has no one-tap install, so show the `manual` section. Also null when the eSIM's activation string is not in the standard `LPA:1$…` form."
              },
              "clip_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InstallInstructionStep"
                }
              }
            },
            "required": [
              "title",
              "install_link",
              "clip_url",
              "steps"
            ]
          },
          "qr_code": {
            "type": "object",
            "description": "Install from a second screen: render `lpa` as a QR code and scan it with the phone. On every phone family this section is Hubby's own English copy, whatever the `locale`, with `clip_url` null and no screenshots.",
            "properties": {
              "title": {
                "type": "string"
              },
              "lpa": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The eSIM's activation string, to render as a QR code."
              },
              "clip_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InstallInstructionStep"
                }
              }
            },
            "required": [
              "title",
              "lpa",
              "clip_url",
              "steps"
            ]
          },
          "manual": {
            "type": "object",
            "description": "Install by typing the SM-DP+ address and the matching id in the phone's settings. The steps are localized on every phone family, in the `locale` where translated, else English.",
            "properties": {
              "title": {
                "type": "string"
              },
              "smdp_address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The SM-DP+ address from the activation string. Null when the string is not in the standard form."
              },
              "matching_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The matching id (activation code) from the activation string. Null when the string is not in the standard form."
              },
              "clip_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InstallInstructionStep"
                }
              }
            },
            "required": [
              "title",
              "smdp_address",
              "matching_id",
              "clip_url",
              "steps"
            ]
          },
          "on_arrival": {
            "type": "array",
            "description": "What to do on arrival: rename the line, turn it on, enable roaming, pick it for mobile data. Show the sections in the order of this list. The order can be different for each family (on Samsung and Xiaomi, mobile data comes before roaming), so find a section by its `id`, not by its position.",
            "items": {
              "$ref": "#/components/schemas/InstallInstructionSection"
            }
          },
          "apn": {
            "description": "Optional help for an eSIM that has signal but no internet: add the access point name (APN) by hand. Show it only then, for example on a \"No internet?\" screen, not in the main install flow. Many eSIMs set the APN automatically and never need it. Null when the phone family does not need it (`iphone` today).",
            "anyOf": [
              {
                "$ref": "#/components/schemas/InstallInstructionApnSetup"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "family",
          "one_tap",
          "qr_code",
          "manual",
          "on_arrival",
          "apn"
        ],
        "title": "InstallInstructionSet"
      },
      "InstallInstructionStep": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "The step, in the response `locale` where its section is translated; the `qr_code` section is English only on every phone family. Tap targets are marked with markdown bold, `**Activate**`, and nothing else; render or strip it."
          },
          "screenshot_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "An image of the screen for this step, in this language. Null when none exists."
          }
        },
        "required": [
          "text",
          "screenshot_url"
        ],
        "title": "InstallInstructionStep"
      },
      "InstallInstructions": {
        "type": "object",
        "properties": {
          "iccid": {
            "type": "string",
            "description": "The eSIM's ICCID."
          },
          "locale": {
            "type": "string",
            "description": "The locale the instructions were asked in: `locale` from the query, else the locale of your latest booking that issued a package on this eSIM (live or terminated), else your `default_locale` (`GET /v1/me`), else `en-US`. A booking's locale is echoed as stored, so this can be a short tag such as `es`. The copy exists in English, German, Spanish and Swedish; for any other language it is English, and this field still echoes the locale asked for.",
            "examples": [
              "de-DE"
            ]
          },
          "esim_label": {
            "type": "string",
            "description": "The name the traveller gives the eSIM on the phone: the Partner name (`name` in `GET /v1/me`) followed by ` eSIM`. The steps already name this label wherever the traveller types or selects it. A Visual identity has no effect on it.",
            "examples": [
              "Acme Travel eSIM"
            ]
          },
          "ios": {
            "type": "array",
            "description": "One entry per iOS phone family: `iphone`.",
            "items": {
              "$ref": "#/components/schemas/InstallInstructionSet"
            }
          },
          "android": {
            "type": "array",
            "description": "One entry per Android phone family: `pixel`, `samsung`, `xiaomi`.",
            "items": {
              "$ref": "#/components/schemas/InstallInstructionSet"
            }
          }
        },
        "required": [
          "iccid",
          "locale",
          "esim_label",
          "ios",
          "android"
        ],
        "title": "InstallInstructions"
      },
      "IssuePackageRequest": {
        "type": "object",
        "description": "The body of an issue call. Send it empty unless the item names no traveller yet.",
        "properties": {
          "external_user_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your id for the traveller who receives this package. Required when the item does not name a traveller yet; the traveller is then bound to the item exactly as at booking. When the item already names one, send the same id or leave it out.",
            "maxLength": 255
          }
        },
        "title": "IssuePackageRequest"
      },
      "IssuedPackage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "description": "Where this package is in its lifecycle: `redeemed` (issued, install material available), `installed` (the eSIM is on a phone), `activated` (live on the network, validity running), `expired` (the validity elapsed, or the provider ended the package after it activated; a package that used up its data stays `activated` until the provider ends it), `terminated` (ended before activation).",
            "$ref": "#/components/schemas/PackageLifecycleState"
          },
          "bundle_id": {
            "type": "string",
            "description": "The bundle this package was issued from."
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the package was issued."
          },
          "installed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this package became installable on a phone: the eSIM's install, or the issue itself when the eSIM was already installed. Null until the eSIM is on a phone."
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the package activated and its validity started: the time the connectivity provider reported the package activated. Hubby does not observe the network attach itself. Null until then."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The latest moment this package can still be activated: the deadline the connectivity provider sets when the package is issued, after which an unused package can no longer be started. It is **not** when the package's validity ends: a package with a few days of validity is typically still activatable for about a year. Use `valid_until` for when the package stops working."
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this package's validity ends and its data stops working: the moment it activated plus its time allowance. Null until the package activates, because the clock starts at activation, not at issue. Also null for a package that carries no time allowance."
          },
          "terminated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the package ended: before activation (`state` is `terminated`), or after activation when the provider ended it (`state` is `expired`). Null while the package has not ended."
          },
          "esim": {
            "$ref": "#/components/schemas/EsimInstallMaterial"
          }
        },
        "required": [
          "id",
          "state",
          "bundle_id",
          "issued_at",
          "installed_at",
          "activated_at",
          "expires_at",
          "valid_until",
          "terminated_at",
          "esim"
        ],
        "title": "IssuedPackage"
      },
      "PackageLifecycleState": {
        "type": "string",
        "description": "The lifecycle state of a booking item and of the package issued from it. Read it instead of the item's `status`. Hubby computes it on each read: from the item until a package is issued, then from the package and from the install of its eSIM.\n",
        "enum": [
          "queued",
          "unavailable",
          "redeemed",
          "installed",
          "activated",
          "expired",
          "terminated",
          "cancelled"
        ],
        "title": "PackageLifecycleState"
      },
      "PackageQueueStatus": {
        "type": "string",
        "enum": [
          "queued",
          "pending_payment",
          "paid",
          "activated",
          "cancelled",
          "reverted",
          "unresolved"
        ],
        "title": "PackageQueueStatus"
      },
      "PackageType": {
        "type": "string",
        "enum": [
          "data_limited",
          "starter",
          "unlimited"
        ],
        "title": "PackageType"
      },
      "Partner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "end_customer_channel": {
            "description": "Where your travellers get their eSIM, which decides what a booking carries. `webapp`: Hubby's web app; each package specification must name the traveller (`email` or `external_user_id`). `native_app`: the Hubby app; travellers redeem the item's `code`. `partner_app`: your own app over this API; you issue and install the package yourself, and booking items carry no `code`, `offer_uuid`, `upgrade_url` or `deep_link_url`.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/EndCustomerChannel"
              },
              {
                "type": "null"
              }
            ]
          },
          "price_list_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The id of the price list your bundle prices are read from, or `null` when none is set on your account. Informational: the prices themselves are on each bundle in `GET /v1/bundles`."
          },
          "package_strategy": {
            "type": [
              "object",
              "null"
            ],
            "description": "The fixed package dimensions applied to every booking you create\nthrough this API. When present, the `package_type`, `size` and\n`package_duration` on every package specification you send are\nreplaced before the booking is stored — your destination, travel\ndates and traveller identities are never changed. The sizes here are canonical units, not the units you send:\n`size_bytes` is what `size` (`\"1GB\"`, `\"512MB\"`) becomes, and\n`duration_seconds` is what `package_duration` (whole days)\nbecomes. A null on either means that dimension does not apply to\nthe type — an unlimited package carries no size, a data-limited\none no duration. That is a dimension you do not choose, not a\npackage without a validity: a data-limited package lasts the\n`duration_days` of the bundle chosen at issue, counted from\nactivation, and `valid_until` on the issued package says when it\nends (null only when that bundle carries no duration).\n\nThis may be set on your account or inherited from a parent\naccount; either way it is what governs your bookings. `null`\nmeans no strategy applies: you specify packages freely.",
            "properties": {
              "package_type": {
                "type": "string"
              },
              "size_bytes": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "duration_seconds": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "package_type",
              "size_bytes",
              "duration_seconds"
            ]
          },
          "force_destinationless": {
            "type": "boolean",
            "description": "Whether every booking you create through this API is stored without a destination, whatever destination you send. When `true`, the `destination` on each package specification is optional and is discarded if you send one — nothing is rejected for naming a destination, and your travel dates, package dimensions and traveller identities are untouched. Each traveller picks their destination when they redeem in the Hubby app, from the destinations available to your account.\n\nHubby never sets this on a Partner App account (`end_customer_channel` `partner_app`): a package issued over this API has no redeem step in which to pick a destination, so your bookings always carry the `destination` you send, and a package specification with neither `destination` nor `bundle_id` is refused at booking with `422` `validation_failed`.\n\nThis may be set on your account or inherited from a parent account; either way it is what governs your bookings. `false` means your bookings keep the destination you send, which is the normal case."
          },
          "default_locale": {
            "type": [
              "string",
              "null"
            ],
            "description": "The language bookings you create through this API are recorded in when you send no `locale` — for example `nl-NL`. Only ever fills a blank. A `locale` you send on a booking is always kept, whatever this says, and a booking you send without one is recorded in this language instead of none. `null` means no default is set on your account: a booking with no `locale` keeps none, exactly as before.\n\nIt applies to the booking itself. Travellers are not switched to this language — a traveller keeps whichever language they have chosen, and one who has chosen none keeps none until they do.\n\nThis is set on your account. Unlike `package_strategy` and `force_destinationless`, it is NOT inherited from a parent account."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "is_active",
          "parent_id",
          "end_customer_channel",
          "price_list_id",
          "package_strategy",
          "force_destinationless",
          "default_locale",
          "created_at",
          "updated_at"
        ],
        "title": "Partner"
      },
      "PartnerTerminateReason": {
        "type": "string",
        "description": "Why you terminated a package you issued.\n",
        "enum": [
          "changed_plans",
          "installation_failed",
          "not_received",
          "other"
        ],
        "title": "PartnerTerminateReason"
      },
      "PresignBookingUploadData": {
        "type": "object",
        "description": "Ask for a presigned URL to upload one dataset file of bookings for a day. The upload is always filed under your own account: the request has no partner field, because the API key that signs it names you.",
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "The day the file is for, `Y-m-d`. One file per dataset and day."
          },
          "dataset": {
            "type": "string",
            "description": "The dataset the file belongs to: letters, digits, `_` and `-`, up to 64 characters. Default `bookings`.",
            "pattern": "^[A-Za-z0-9_-]{1,64}$"
          },
          "overwrite": {
            "type": "boolean",
            "description": "Replace the file already uploaded for this dataset and day. Default `false`: an existing file is then refused with 409 `upload_already_exists`."
          }
        },
        "required": [
          "date"
        ],
        "title": "PresignBookingUploadData"
      },
      "RedirectToken": {
        "type": "object",
        "properties": {
          "redirect_token": {
            "type": "string"
          },
          "expires_in": {
            "type": "integer",
            "description": "Lifetime of the redirect token in seconds."
          }
        },
        "required": [
          "redirect_token",
          "expires_in"
        ],
        "title": "RedirectToken"
      },
      "SupportedDevice": {
        "type": "object",
        "properties": {
          "brand": {
            "type": "string",
            "description": "The brand as Hubby's list spells it. Send it as `phone_brand` to the check."
          },
          "name": {
            "type": "string",
            "description": "The device name as Hubby's list spells it. Show this name to the traveller to pick from, and send the picked name as `phone_model` to the check."
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "A model code for the device, for display only. Null when the list has none. One code can name different phones, so do not use it to identify a phone."
          },
          "year": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The year the model came out. Null when the list does not know."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Device note: a free-text caveat for the model, for example \"US: eSIM only\" or \"Wi-Fi + Cellular only\". Regional limits are here. Show it to the traveller as it is. Null when the list has no note."
          }
        },
        "required": [
          "brand",
          "name",
          "model",
          "year",
          "note"
        ],
        "title": "SupportedDevice"
      },
      "SupportedLocale": {
        "type": "string",
        "description": "The locale tags `locale` accepts on `GET /v1/esims/{iccid}/instructions`: BCP-47 language-region tags. A tag outside this list is refused with 422. A booking `locale` is freer: it can name a language outside this list, for example `fi` or `ja`, and the instructions follow it when copy exists in that language. Copy without a translation in the requested language is English.\n",
        "enum": [
          "en-US",
          "en-EU",
          "en-GB",
          "en-CA",
          "nl-NL",
          "de-DE",
          "fr-FR",
          "fr-CA",
          "it-IT",
          "es-ES",
          "cs-CZ",
          "pl-PL",
          "pt-PT",
          "fr-BE",
          "nl-BE",
          "de-AT",
          "de-CH",
          "fr-CH",
          "it-CH",
          "sv-SE",
          "sk-SK",
          "de-BE",
          "en-AU",
          "da-DK",
          "ko-KR",
          "hu-HU",
          "no-NO",
          "pt-BR",
          "en-NZ",
          "zh-CN",
          "ru-RU",
          "ro-RO"
        ],
        "title": "SupportedLocale"
      },
      "UpdateBookingRequest": {
        "type": "object",
        "description": "The booking fields to change. Every field is optional: an omitted field is left as it is, and an explicit `null` clears it.",
        "properties": {
          "external_reference": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "external_reference_secondary": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "visual_identity_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "depart_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trip's departure day, written `DD/MM/YYYY`, `DD-MM-YYYY` or `YYYY-MM-DD` (day first; an ISO 8601 date-time is accepted too). Changing it reschedules the booking. If Hubby sends pre-departure emails for your account, they follow the new day. Compared by calendar day, so resending the day the booking already has changes nothing. Answers 422 when the booking has already departed, when the day is before today, when it is `null` for a booking that has a departure, or when the booking is cancelled."
          },
          "return_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trip's return day, written `DD/MM/YYYY`, `DD-MM-YYYY` or `YYYY-MM-DD` (day first; an ISO 8601 date-time is accepted too), on or after the departure. Changing it reschedules the booking, and may still be done once the trip is under way. Answers 422 for a cancelled booking."
          },
          "locale": {
            "type": [
              "string",
              "null"
            ],
            "description": "The booking's language as a BCP-47 tag. `es`, `es-ES` and `es_es` are all accepted and stored as one normalised tag. Up to 10 characters: a longer value is refused with 422. A value Hubby cannot read as a language is stored as `null` with no error, and your `default_locale` (`GET /v1/me`) then fills it.",
            "maxLength": 10
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 255
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The traveller's date of birth, a day before today, written `YYYY-MM-DD`. Answers return it as `YYYY-MM-DD`."
          },
          "pax": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many travellers the booking is for. A headcount only: it creates no packages. Send one package specification per traveller.",
            "minimum": 1,
            "maximum": 2147483647
          },
          "flight_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "departure_location": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "communication_options": {
            "type": [
              "object",
              "null"
            ],
            "description": "Your messaging preferences for the traveller. Hubby stores and returns it; it does not control Hubby's messages. `should_send_message` is required when the object is sent; `channels` is optional.",
            "properties": {
              "should_send_message": {
                "type": "boolean"
              },
              "channels": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "$ref": "#/components/schemas/CommunicationChannel"
                }
              }
            },
            "required": [
              "should_send_message"
            ]
          }
        },
        "title": "UpdateBookingRequest"
      },
      "UpdatePackageSpecificationRequest": {
        "type": "object",
        "description": "The new specification for a booking's package items. The same body updates one item (its id in the path) or every item of the booking (no id in the path). Send only the fields you change.",
        "properties": {
          "destination": {
            "type": [
              "string",
              "null"
            ],
            "description": "The country or region the item is for: an ISO 3166-1 alpha-2 or alpha-3 code, a region's code, or the destination's English name, in any letter case. The value must match exactly: a translated name, a typo or any other value answers 422.",
            "maxLength": 120
          },
          "package_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "`data-limited` (or `data_limited`), `starter` or `unlimited`. Both spellings are accepted; answers always spell it `data_limited`.",
            "enum": [
              "data_limited",
              "data-limited",
              "starter",
              "unlimited",
              null
            ]
          },
          "size": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d+(\\.\\d+)?(MB|GB)$"
          },
          "package_duration": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "bundle_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "A bundle `id` from the catalogue (`GET /v1/bundles`). The item takes that bundle's destination, type, size and duration, and is provisioned with exactly that bundle; `destination`, `package_type`, `size` and `package_duration` are ignored when sent with it. A bundle outside your catalogue answers 422. Omit it to re-specify by dimensions, which lets go of any bundle named before. Refused with 422 when `GET /v1/me` reports a `package_strategy`, or when your bookings are made without a destination: those settings choose the bundle for you. Also refused with 422 on an item Hubby support granted, whose bundle support chose."
          }
        },
        "title": "UpdatePackageSpecificationRequest"
      }
    }
  }
}