{
  "openapi": "3.1.0",
  "info": {
    "title": "One Bag Travel data API",
    "version": "1.0.0",
    "summary": "Carry-on travel backpack specifications, airline carry-on limits, and one-bag packing lists.",
    "description": "Read-only JSON snapshots of the One Bag Travel database, rebuilt on every deploy. No authentication, no rate limit, no query parameters — each endpoint returns its full collection and is meant to be fetched once and filtered client-side. For server-side filtering use the MCP server at https://onebag.travel/mcp (card: /.well-known/mcp.json).",
    "contact": { "url": "https://onebag.travel/contact/" },
    "license": { "name": "Free to use with attribution to https://onebag.travel", "url": "https://onebag.travel/terms/" }
  },
  "servers": [{ "url": "https://onebag.travel/api", "description": "Production" }],
  "externalDocs": { "url": "https://onebag.travel/api/", "description": "Human-readable API documentation" },
  "paths": {
    "/carryons.json": {
      "get": {
        "operationId": "listCarryOnBackpacks",
        "summary": "Every carry-on travel backpack in the database",
        "description": "Specifications for ~870 carry-on travel backpacks, including discontinued models (`discontinued: true`).",
        "responses": {
          "200": {
            "description": "The full backpack collection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["generated", "count", "backpacks"],
                  "properties": {
                    "generated": { "type": "string", "format": "date", "description": "Build date of this snapshot." },
                    "count": { "type": "integer" },
                    "backpacks": { "type": "array", "items": { "$ref": "#/components/schemas/Backpack" } }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/airlines.json": {
      "get": {
        "operationId": "listAirlineCarryOnRules",
        "summary": "Carry-on size and weight limits for ~200 airlines",
        "description": "Each airline carries the official source URL its limits were read from, and the date the record was last checked. Airlines change baggage policy without notice: treat `sourceUrl` as the authority.",
        "responses": {
          "200": {
            "description": "The full airline collection, sorted by name.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["generated", "count", "airlines"],
                  "properties": {
                    "generated": { "type": "string", "format": "date" },
                    "count": { "type": "integer" },
                    "airlines": { "type": "array", "items": { "$ref": "#/components/schemas/Airline" } }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/packing-lists.json": {
      "get": {
        "operationId": "listPackingLists",
        "summary": "Published one-bag packing lists",
        "description": "Summaries of real packing lists. The items on a list are served on the list's own page — request it with `Accept: text/markdown`, or use the MCP `get_packing_list` tool.",
        "responses": {
          "200": {
            "description": "The published packing lists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["generated", "count", "packingLists"],
                  "properties": {
                    "generated": { "type": "string", "format": "date" },
                    "count": { "type": "integer" },
                    "packingLists": { "type": "array", "items": { "$ref": "#/components/schemas/PackingList" } }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Dimensions": {
        "type": "object",
        "description": "Null on any axis the source did not publish.",
        "properties": {
          "length": { "type": ["number", "null"] },
          "width": { "type": ["number", "null"] },
          "height": { "type": ["number", "null"] }
        }
      },
      "Feature": {
        "description": "true / false where the field is only ever yes-or-no, a descriptive string where the source records something richer (\"removable\", \"17in\", \"2, side\"), null where it was never recorded.",
        "oneOf": [{ "type": "boolean" }, { "type": "string" }, { "type": "null" }]
      },
      "Backpack": {
        "type": "object",
        "required": ["id", "name", "url"],
        "properties": {
          "id": { "type": "string", "description": "Stable id; also the first path segment of `url`." },
          "name": { "type": ["string", "null"] },
          "brand": { "type": ["string", "null"] },
          "url": { "type": "string", "format": "uri", "description": "Canonical page. Serves Markdown on `Accept: text/markdown`." },
          "capacityLitres": { "type": ["number", "null"] },
          "expandedCapacityLitres": { "type": ["number", "null"], "description": "Null when the bag does not expand." },
          "weightKg": { "type": ["number", "null"], "description": "Empty weight of the bag itself." },
          "weightLb": { "type": ["number", "null"] },
          "dimensionsCm": { "$ref": "#/components/schemas/Dimensions" },
          "dimensionsIn": { "$ref": "#/components/schemas/Dimensions" },
          "airlineCompatibilityPercent": {
            "type": ["number", "null"],
            "description": "Share of the assessed airlines whose carry-on limits this bag is within, 0-100."
          },
          "airlineCompatibilityAssessedCount": { "type": ["integer", "null"] },
          "features": {
            "type": "object",
            "properties": {
              "laptop": { "$ref": "#/components/schemas/Feature" },
              "compression": { "$ref": "#/components/schemas/Feature" },
              "hipBelt": { "$ref": "#/components/schemas/Feature" },
              "sternumStrap": { "$ref": "#/components/schemas/Feature" },
              "passthrough": { "$ref": "#/components/schemas/Feature" },
              "hardShell": { "$ref": "#/components/schemas/Feature" },
              "rainfly": { "$ref": "#/components/schemas/Feature" },
              "waterResistant": { "$ref": "#/components/schemas/Feature" },
              "passportPocket": { "$ref": "#/components/schemas/Feature" },
              "waterBottlePockets": { "$ref": "#/components/schemas/Feature" },
              "compartments": { "type": ["integer", "null"] },
              "openingType": { "type": "string", "enum": ["Other", "Clamshell", "Rolltop", "TopFlap", "TopZip"] },
              "material": { "type": ["string", "null"] }
            }
          },
          "priceUsd": { "type": ["number", "null"], "description": "Last recorded price. Not live." },
          "discontinued": { "type": "boolean" },
          "imageUrl": { "type": ["string", "null"], "format": "uri" },
          "updated": { "type": ["string", "null"], "format": "date" }
        }
      },
      "Airline": {
        "type": "object",
        "required": ["id", "name", "url"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": ["string", "null"] },
          "url": { "type": "string", "format": "uri" },
          "maxDimensionsCm": { "$ref": "#/components/schemas/Dimensions" },
          "maxDimensionsIn": { "$ref": "#/components/schemas/Dimensions" },
          "maxLinearCm": { "type": ["number", "null"], "description": "Combined length+width+height limit, where the airline states one instead of per-side limits." },
          "maxLinearIn": { "type": ["number", "null"] },
          "maxWeightKg": { "type": ["number", "null"], "description": "Null means the airline publishes no weight limit — not that the limit is unlimited." },
          "maxWeightLb": { "type": ["number", "null"] },
          "weightIncludesPersonalItem": { "type": "boolean", "description": "True when the weight limit covers carry-on plus personal item together." },
          "doublesForUpgradedSeat": { "type": "boolean" },
          "originalMeasurementsInMetric": { "type": "boolean", "description": "Which unit the airline published; the other set is converted." },
          "notes": { "type": ["string", "null"] },
          "sourceUrl": { "type": ["string", "null"], "format": "uri", "description": "The airline's own baggage page. The authority for these limits." },
          "updated": { "type": ["string", "null"], "format": "date" }
        }
      },
      "PackingList": {
        "type": "object",
        "required": ["id", "title", "url"],
        "properties": {
          "id": { "type": "string" },
          "title": { "type": ["string", "null"] },
          "url": { "type": "string", "format": "uri" },
          "destination": { "type": ["string", "null"] },
          "durationDays": { "type": ["integer", "null"] },
          "minTemperatureC": { "type": ["number", "null"] },
          "maxTemperatureC": { "type": ["number", "null"] },
          "itemCount": { "type": ["integer", "null"] },
          "female": { "type": "boolean" },
          "backpack": {
            "type": ["object", "null"],
            "properties": {
              "id": { "type": ["string", "null"] },
              "name": { "type": ["string", "null"] },
              "url": { "type": ["string", "null"], "format": "uri" }
            }
          },
          "sourceUrl": { "type": ["string", "null"], "format": "uri", "description": "Where the list was originally published, usually a forum post." },
          "updated": { "type": ["string", "null"], "format": "date" }
        }
      }
    }
  }
}
