{
  "openapi": "3.1.0",
  "info": {
    "title": "Gapwise Public Campus API",
    "version": "1.0.0",
    "description": "Authoritative OpenAPI 3.1 contract for the unauthenticated Gapwise Public Campus API v1. API version, campus data versions, and SDK package versions evolve independently. Additive fields and operations are non-breaking; removing or changing documented fields, semantics, or status codes requires a new API version. No timetable, friend, account, authentication, private-sync, AI-delegation, or precise live-location data is exposed.",
    "contact": {
      "name": "Gapwise",
      "url": "https://gapwise.ca/developers"
    }
  },
  "servers": [
    {
      "url": "https://api.gapwise.ca/v1",
      "description": "Canonical production API"
    }
  ],
  "tags": [
    {
      "name": "Metadata",
      "description": "API capabilities, versions, and privacy boundary."
    },
    {
      "name": "Buildings",
      "description": "Canonical UTM building identity and conservative accessibility/provenance facts."
    },
    {
      "name": "Places",
      "description": "Source-backed campus places, discovery, hours provenance, and explicit availability uncertainty."
    },
    {
      "name": "Routing",
      "description": "Deterministic building-level campus routes."
    },
    {
      "name": "Gap planning",
      "description": "Route-aware assessment of an explicitly supplied free interval; no timetable is accepted or stored."
    },
    {
      "name": "Legacy compatibility",
      "description": "Deprecated gapwise.ca aliases retained for existing consumers."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getApiInfo",
        "summary": "Get API capabilities, versions, and privacy boundary",
        "responses": {
          "200": {
            "description": "API metadata.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiInfo"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/buildings": {
      "get": {
        "tags": [
          "Buildings"
        ],
        "operationId": "listBuildings",
        "summary": "Search and list canonical UTM buildings",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring search across canonical code, name, and aliases. Unicode is normalized before matching.",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "example": "instructional"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "enum": [
                "academic",
                "residence",
                "facility"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Zero-based deterministic collection offset.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deterministically ordered building page.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BuildingsCollection"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "405": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/routes": {
      "post": {
        "tags": [
          "Routing"
        ],
        "operationId": "calculateRoute",
        "summary": "Calculate a deterministic building-level route",
        "description": "Always inspect status, routeVerification, accuracy, and warnings. An unavailable step-free result is safer than an invented accessible route.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RouteRequest"
              },
              "example": {
                "from": "MN",
                "to": "IB",
                "preferences": {
                  "mode": "fastest"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Route result.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          },
          "405": {
            "$ref": "#/components/responses/ApiError"
          },
          "409": {
            "$ref": "#/components/responses/ApiError"
          },
          "413": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          },
          "415": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/gaps/plan": {
      "post": {
        "tags": [
          "Gap planning"
        ],
        "operationId": "planGap",
        "summary": "Assess an explicitly supplied free interval",
        "description": "Accepts only an explicit interval and boundary buildings. It does not accept, retrieve, or store a timetable.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GapPlanRequest"
              },
              "example": {
                "from": "MN",
                "to": "IB",
                "term": "Fall",
                "weekday": "Wednesday",
                "startTime": 660,
                "endTime": 780
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Route-aware deterministic gap assessment.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GapPlanResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          },
          "405": {
            "$ref": "#/components/responses/ApiError"
          },
          "409": {
            "$ref": "#/components/responses/ApiError"
          },
          "413": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          },
          "415": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/places": {
      "get": {
        "tags": [
          "Places"
        ],
        "operationId": "listPlaces",
        "summary": "Discover source-backed campus places",
        "description": "Filters combine with AND semantics. openNow=unknown is the safe way to discover places whose operating state cannot be determined. Results with unknown availability are never treated as closed.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring search across place names and amenities.",
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "enum": [
                "dining",
                "study",
                "library",
                "service",
                "recreation",
                "amenity",
                "facility"
              ]
            }
          },
          {
            "name": "building",
            "in": "query",
            "description": "Canonical building code.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Z0-9]{1,12}$"
            }
          },
          {
            "name": "openNow",
            "in": "query",
            "schema": {
              "enum": [
                "open",
                "closed",
                "unknown"
              ]
            },
            "description": "Filter by evaluated availability. Requests using this time-dependent filter are not cached."
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Zero-based deterministic collection offset.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deterministically ordered place page with source-dependent availability.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlacesCollection"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "405": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/buildings/{building}": {
      "get": {
        "tags": [
          "Buildings"
        ],
        "operationId": "getBuilding",
        "summary": "Resolve one building by canonical code, exact name, or alias",
        "parameters": [
          {
            "name": "building",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 240
            },
            "example": "MN"
          }
        ],
        "responses": {
          "200": {
            "description": "Resolved canonical building.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BuildingResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          },
          "405": {
            "$ref": "#/components/responses/ApiError"
          },
          "409": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/places/{placeId}": {
      "get": {
        "tags": [
          "Places"
        ],
        "operationId": "getPlace",
        "summary": "Get one canonical campus place",
        "parameters": [
          {
            "name": "placeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
            },
            "example": "utm-library"
          }
        ],
        "responses": {
          "200": {
            "description": "Source-backed campus place.",
            "headers": {
              "X-Request-Id": {
                "description": "Opaque request identifier for support and diagnostics.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Response caching policy.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApiError"
          },
          "404": {
            "$ref": "#/components/responses/ApiError"
          },
          "405": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/ApiError"
          }
        }
      }
    },
    "/api/utm-buildings": {
      "get": {
        "tags": [
          "Legacy compatibility"
        ],
        "operationId": "listUtmBuildings",
        "summary": "List canonical UTM buildings",
        "responses": {
          "200": {
            "description": "Canonical building inventory with routing coverage and provenance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "buildings"
                  ],
                  "properties": {
                    "service": {
                      "const": "gapwise-public-campus"
                    },
                    "buildings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Building"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "deprecated": true,
        "servers": [
          {
            "url": "https://gapwise.ca",
            "description": "Legacy compatibility origin"
          }
        ],
        "description": "Backwards-compatible alias; new integrations should use api.gapwise.ca/v1."
      }
    },
    "/api/utm-building": {
      "get": {
        "tags": [
          "Legacy compatibility"
        ],
        "operationId": "getUtmBuilding",
        "summary": "Resolve one canonical UTM building",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "Canonical building code, name, or recognized alias."
          }
        ],
        "responses": {
          "200": {
            "description": "Resolved building.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "building"
                  ],
                  "properties": {
                    "service": {
                      "const": "gapwise-public-campus"
                    },
                    "building": {
                      "$ref": "#/components/schemas/Building"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/LegacyApiError"
          },
          "409": {
            "$ref": "#/components/responses/LegacyApiError"
          }
        },
        "deprecated": true,
        "servers": [
          {
            "url": "https://gapwise.ca",
            "description": "Legacy compatibility origin"
          }
        ],
        "description": "Backwards-compatible alias; new integrations should use api.gapwise.ca/v1."
      }
    },
    "/api/utm-route": {
      "post": {
        "tags": [
          "Legacy compatibility"
        ],
        "operationId": "routeBetweenUtmBuildings",
        "summary": "Plan a deterministic building-to-building campus route",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RouteRequest"
              },
              "examples": {
                "mnToIb": {
                  "value": {
                    "from": "MN",
                    "to": "IB"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Route result. Always inspect status, accuracy, routeVerification, and warnings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "route"
                  ],
                  "properties": {
                    "service": {
                      "const": "gapwise-public-campus"
                    },
                    "route": {
                      "$ref": "#/components/schemas/Route"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/LegacyApiError"
          },
          "404": {
            "$ref": "#/components/responses/LegacyApiError"
          },
          "409": {
            "$ref": "#/components/responses/LegacyApiError"
          }
        },
        "deprecated": true,
        "servers": [
          {
            "url": "https://gapwise.ca",
            "description": "Legacy compatibility origin"
          }
        ],
        "description": "Backwards-compatible alias; new integrations should use api.gapwise.ca/v1."
      }
    },
    "/api/utm-gap-plan": {
      "post": {
        "tags": [
          "Legacy compatibility"
        ],
        "operationId": "planUtmGap",
        "summary": "Assess a bounded campus gap with deterministic route and timing logic",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GapPlanRequest"
              },
              "examples": {
                "wednesdayTwoHourGap": {
                  "value": {
                    "from": "MN",
                    "to": "IB",
                    "term": "Fall",
                    "weekday": "Wednesday",
                    "startTime": 660,
                    "endTime": 780
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deterministic gap assessment and underlying route.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "gapPlan"
                  ],
                  "properties": {
                    "service": {
                      "const": "gapwise-public-campus"
                    },
                    "gapPlan": {
                      "$ref": "#/components/schemas/GapPlan"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/LegacyApiError"
          },
          "404": {
            "$ref": "#/components/responses/LegacyApiError"
          },
          "409": {
            "$ref": "#/components/responses/LegacyApiError"
          }
        },
        "deprecated": true,
        "servers": [
          {
            "url": "https://gapwise.ca",
            "description": "Legacy compatibility origin"
          }
        ],
        "description": "Backwards-compatible alias; new integrations should use api.gapwise.ca/v1."
      }
    },
    "/api/utm-places": {
      "get": {
        "tags": [
          "Legacy compatibility"
        ],
        "operationId": "listUtmPlaces",
        "summary": "List source-backed UTM campus places",
        "responses": {
          "200": {
            "description": "Versioned place snapshot with explicit provenance and freshness.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "dataVersion",
                    "generatedAt",
                    "places"
                  ],
                  "properties": {
                    "service": {
                      "const": "gapwise-public-campus"
                    },
                    "dataVersion": {
                      "type": "string"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "places": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LegacyCampusPlace"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "deprecated": true,
        "servers": [
          {
            "url": "https://gapwise.ca",
            "description": "Legacy compatibility origin"
          }
        ],
        "description": "Backwards-compatible alias; new integrations should use api.gapwise.ca/v1."
      }
    },
    "/api/utm-place": {
      "get": {
        "tags": [
          "Legacy compatibility"
        ],
        "operationId": "getUtmPlace",
        "summary": "Look up one canonical campus place",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Canonical place and its source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "dataVersion",
                    "place"
                  ],
                  "properties": {
                    "service": {
                      "const": "gapwise-public-campus"
                    },
                    "dataVersion": {
                      "type": "string"
                    },
                    "place": {
                      "$ref": "#/components/schemas/LegacyCampusPlace"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/LegacyApiError"
          }
        },
        "deprecated": true,
        "servers": [
          {
            "url": "https://gapwise.ca",
            "description": "Legacy compatibility origin"
          }
        ],
        "description": "Backwards-compatible alias; new integrations should use api.gapwise.ca/v1."
      }
    }
  },
  "components": {
    "schemas": {
      "Term": {
        "type": "string",
        "enum": [
          "Fall",
          "Winter",
          "Summer"
        ]
      },
      "Weekday": {
        "type": "string",
        "enum": [
          "Monday",
          "Tuesday",
          "Wednesday",
          "Thursday",
          "Friday",
          "Saturday",
          "Sunday"
        ]
      },
      "Provenance": {
        "type": "object",
        "required": [
          "source",
          "sourceUrl",
          "lastVerified",
          "verificationStatus"
        ],
        "properties": {
          "source": {
            "type": "string"
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri"
          },
          "lastVerified": {
            "type": "string"
          },
          "verificationStatus": {
            "type": "string",
            "enum": [
              "verified",
              "inferred",
              "unknown"
            ]
          }
        }
      },
      "Building": {
        "type": "object",
        "required": [
          "code",
          "name",
          "category",
          "aliases",
          "routingCoverage",
          "entranceCount",
          "verifiedEntranceCount",
          "accessibility",
          "indoorRoomNodeCount",
          "provenance"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "academic",
              "residence",
              "facility"
            ]
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "routingCoverage": {
            "type": "string",
            "enum": [
              "mapped",
              "identity-only"
            ]
          },
          "entranceCount": {
            "type": "integer",
            "minimum": 0
          },
          "verifiedEntranceCount": {
            "type": "integer",
            "minimum": 0
          },
          "accessibility": {
            "type": "string",
            "enum": [
              "accessible",
              "not_accessible",
              "unknown"
            ]
          },
          "indoorRoomNodeCount": {
            "type": "integer",
            "minimum": 0
          },
          "provenance": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Provenance"
            }
          }
        }
      },
      "RoutePreferencesInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "fastest",
              "prefer-indoor",
              "step-free"
            ]
          },
          "walkingSpeedMps": {
            "type": "number"
          },
          "transitionBufferMinutes": {
            "type": "number"
          }
        }
      },
      "RoutePreferences": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "walkingSpeedMps",
          "transitionBufferMinutes"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "fastest",
              "prefer-indoor",
              "step-free"
            ]
          },
          "walkingSpeedMps": {
            "type": "number"
          },
          "transitionBufferMinutes": {
            "type": "number"
          }
        }
      },
      "RouteRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "preferences": {
            "$ref": "#/components/schemas/RoutePreferencesInput"
          }
        }
      },
      "Route": {
        "type": "object",
        "required": [
          "dataVersion",
          "from",
          "to",
          "preferences",
          "status",
          "accuracy",
          "totalDistanceMeters",
          "indoorDistanceMeters",
          "outdoorDistanceMeters",
          "estimatedSeconds",
          "floorChanges",
          "warnings",
          "routeVerification"
        ],
        "properties": {
          "dataVersion": {
            "type": "string"
          },
          "from": {
            "$ref": "#/components/schemas/Building"
          },
          "to": {
            "$ref": "#/components/schemas/Building"
          },
          "preferences": {
            "$ref": "#/components/schemas/RoutePreferences"
          },
          "status": {
            "type": "string",
            "enum": [
              "same-building",
              "routed",
              "approximate",
              "unavailable"
            ]
          },
          "accuracy": {
            "type": "string",
            "enum": [
              "Same building",
              "Verified outdoor route, indoor estimate",
              "Mapped campus path, indoor estimate",
              "Approximate building-to-building estimate",
              "Location unavailable"
            ]
          },
          "totalDistanceMeters": {
            "type": [
              "number",
              "null"
            ]
          },
          "indoorDistanceMeters": {
            "type": [
              "number",
              "null"
            ]
          },
          "outdoorDistanceMeters": {
            "type": [
              "number",
              "null"
            ]
          },
          "estimatedSeconds": {
            "type": [
              "number",
              "null"
            ]
          },
          "floorChanges": {
            "type": [
              "integer",
              "null"
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "routeVerification": {
            "type": "string",
            "enum": [
              "verified",
              "mixed",
              "inferred",
              "unavailable"
            ]
          }
        }
      },
      "GapPreferencesInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "setupMinutes": {
            "type": "number"
          },
          "packUpMinutes": {
            "type": "number"
          },
          "lunchWindowStart": {
            "type": "number"
          },
          "lunchWindowEnd": {
            "type": "number"
          },
          "mealDurationMinutes": {
            "type": "number"
          },
          "willingToLeaveCampus": {
            "type": "boolean"
          },
          "oneWayHomeCommuteMinutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "minimumHomeStayMinutes": {
            "type": "number"
          },
          "homeTurnaroundMinutes": {
            "type": "number"
          },
          "riskTolerance": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          }
        }
      },
      "GapPreferences": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "setupMinutes",
          "packUpMinutes",
          "lunchWindowStart",
          "lunchWindowEnd",
          "mealDurationMinutes",
          "willingToLeaveCampus",
          "oneWayHomeCommuteMinutes",
          "minimumHomeStayMinutes",
          "homeTurnaroundMinutes",
          "riskTolerance"
        ],
        "properties": {
          "setupMinutes": {
            "type": "number"
          },
          "packUpMinutes": {
            "type": "number"
          },
          "lunchWindowStart": {
            "type": "number"
          },
          "lunchWindowEnd": {
            "type": "number"
          },
          "mealDurationMinutes": {
            "type": "number"
          },
          "willingToLeaveCampus": {
            "type": "boolean"
          },
          "oneWayHomeCommuteMinutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "minimumHomeStayMinutes": {
            "type": "number"
          },
          "homeTurnaroundMinutes": {
            "type": "number"
          },
          "riskTolerance": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          }
        }
      },
      "GapPlanRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to",
          "term",
          "weekday",
          "startTime",
          "endTime"
        ],
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "term": {
            "$ref": "#/components/schemas/Term"
          },
          "weekday": {
            "$ref": "#/components/schemas/Weekday"
          },
          "startTime": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440
          },
          "endTime": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440
          },
          "routePreferences": {
            "$ref": "#/components/schemas/RoutePreferencesInput"
          },
          "gapPreferences": {
            "$ref": "#/components/schemas/GapPreferencesInput"
          }
        }
      },
      "Gap": {
        "type": "object",
        "required": [
          "term",
          "weekday",
          "startTime",
          "endTime",
          "durationMinutes",
          "from",
          "to"
        ],
        "properties": {
          "term": {
            "$ref": "#/components/schemas/Term"
          },
          "weekday": {
            "$ref": "#/components/schemas/Weekday"
          },
          "startTime": {
            "type": "integer"
          },
          "endTime": {
            "type": "integer"
          },
          "durationMinutes": {
            "type": "integer",
            "minimum": 0
          },
          "from": {
            "$ref": "#/components/schemas/Building"
          },
          "to": {
            "$ref": "#/components/schemas/Building"
          }
        }
      },
      "GapTimelineSegment": {
        "type": "object",
        "required": [
          "kind",
          "label",
          "minutes"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "setup",
              "activity",
              "travel",
              "buffer",
              "flex"
            ]
          },
          "label": {
            "type": "string"
          },
          "minutes": {
            "type": "number",
            "minimum": 0
          }
        }
      },
      "GapRecommendation": {
        "type": "object",
        "required": [
          "id",
          "action",
          "title",
          "summary",
          "score",
          "activityMinutes",
          "reasons",
          "tags",
          "timeline"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "tight-transition",
              "quick-reset",
              "focus-sprint",
              "meal-window",
              "study-block",
              "deep-work-block",
              "flexible-long-gap",
              "leave-campus-candidate",
              "go-home",
              "location-dependent"
            ]
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "score": {
            "type": "number"
          },
          "activityMinutes": {
            "type": "number",
            "minimum": 0
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "same-room",
                "same-building",
                "nearby-route",
                "lunch-time",
                "route-verified",
                "route-estimated",
                "route-unavailable",
                "location-unknown",
                "high-transition-risk",
                "indoor-route",
                "step-free-route",
                "good-for-commuting"
              ]
            }
          },
          "timeline": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GapTimelineSegment"
            }
          }
        }
      },
      "GapAssessment": {
        "type": "object",
        "required": [
          "primary",
          "alternatives",
          "confidence",
          "confidenceLabel",
          "travelMinutes",
          "bufferMinutes",
          "leaveByMinutes",
          "arrivalMinutes",
          "fallback",
          "routeStatus",
          "routeAccuracy",
          "warnings"
        ],
        "properties": {
          "primary": {
            "$ref": "#/components/schemas/GapRecommendation"
          },
          "alternatives": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GapRecommendation"
            }
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "confidenceLabel": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "travelMinutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "bufferMinutes": {
            "type": "number",
            "minimum": 0
          },
          "leaveByMinutes": {
            "type": "number"
          },
          "arrivalMinutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "fallback": {
            "type": "boolean"
          },
          "routeStatus": {
            "type": "string",
            "enum": [
              "routed",
              "approximate",
              "same-room",
              "unavailable"
            ]
          },
          "routeAccuracy": {
            "type": "string",
            "enum": [
              "Verified indoor + outdoor route",
              "Verified outdoor route, indoor estimate",
              "Mapped campus path, indoor estimate",
              "Approximate building-to-building estimate",
              "Location unavailable"
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "GapPlan": {
        "type": "object",
        "required": [
          "dataVersion",
          "gap",
          "route",
          "gapPreferences",
          "assessment"
        ],
        "properties": {
          "dataVersion": {
            "type": "string"
          },
          "gap": {
            "$ref": "#/components/schemas/Gap"
          },
          "route": {
            "$ref": "#/components/schemas/Route"
          },
          "gapPreferences": {
            "$ref": "#/components/schemas/GapPreferences"
          },
          "assessment": {
            "$ref": "#/components/schemas/GapAssessment"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "CampusFactProvenance": {
        "type": "object",
        "required": [
          "sourceId",
          "status",
          "observedAt"
        ],
        "properties": {
          "sourceId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "verified",
              "stale",
              "inferred",
              "user-reported",
              "unavailable",
              "unknown"
            ]
          },
          "observedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "CampusPlace": {
        "type": "object",
        "required": [
          "id",
          "name",
          "kind",
          "buildingCode",
          "summary",
          "amenities",
          "hoursProvenance",
          "metadataProvenance",
          "availability"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "dining",
              "study",
              "library",
              "service",
              "recreation",
              "amenity",
              "facility"
            ]
          },
          "buildingCode": {
            "type": "string"
          },
          "floorOrRoom": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hoursProvenance": {
            "$ref": "#/components/schemas/CampusFactProvenance"
          },
          "metadataProvenance": {
            "$ref": "#/components/schemas/CampusFactProvenance"
          },
          "availability": {
            "$ref": "#/components/schemas/PlaceAvailability"
          }
        }
      },
      "ApiInfo": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "apiVersion",
          "campusDataVersion",
          "campusStateVersion",
          "authentication",
          "documentationUrl",
          "openapiUrl",
          "capabilities",
          "privacy"
        ],
        "properties": {
          "name": {
            "const": "Gapwise Public Campus API"
          },
          "apiVersion": {
            "const": "v1"
          },
          "campusDataVersion": {
            "type": "string"
          },
          "campusStateVersion": {
            "type": "string"
          },
          "authentication": {
            "const": "none",
            "description": "Public v1 requires no API key or user session."
          },
          "documentationUrl": {
            "type": "string",
            "format": "uri"
          },
          "openapiUrl": {
            "type": "string",
            "format": "uri"
          },
          "capabilities": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "buildingSearch",
              "placeSearch",
              "placeAvailability",
              "routingModes"
            ],
            "properties": {
              "buildingSearch": {
                "type": "boolean"
              },
              "placeSearch": {
                "type": "boolean"
              },
              "placeAvailability": {
                "enum": [
                  "source-dependent"
                ]
              },
              "routingModes": {
                "type": "array",
                "items": {
                  "enum": [
                    "fastest",
                    "prefer-indoor",
                    "step-free"
                  ]
                }
              }
            }
          },
          "privacy": {
            "type": "string"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error",
          "meta"
        ],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "invalid_query",
                  "building_not_found",
                  "rate_limited"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          },
          "meta": {
            "$ref": "#/components/schemas/ErrorMeta"
          }
        }
      },
      "LegacyError": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Building"
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "limit",
          "offset",
          "count",
          "total",
          "nextOffset"
        ],
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "nextOffset": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          }
        }
      },
      "ResponseMeta": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "apiVersion",
          "dataVersion",
          "requestId"
        ],
        "properties": {
          "apiVersion": {
            "const": "v1"
          },
          "dataVersion": {
            "type": "string",
            "minLength": 1
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "requestId": {
            "type": "string",
            "minLength": 1
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          },
          "filters": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "ErrorMeta": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "apiVersion",
          "requestId"
        ],
        "properties": {
          "apiVersion": {
            "const": "v1"
          },
          "requestId": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "PlaceAvailability": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "state",
          "freshness",
          "evaluatedAt",
          "nextTransition"
        ],
        "description": "Unknown is a first-class result and must never be presented as closed. Open/closed is returned only when published hours and provenance permit it.",
        "properties": {
          "state": {
            "enum": [
              "open",
              "closed",
              "unknown"
            ]
          },
          "freshness": {
            "enum": [
              "verified",
              "stale",
              "inferred",
              "user-reported",
              "unavailable",
              "unknown"
            ]
          },
          "evaluatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "nextTransition": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "DataApiInfo": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ApiInfo"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        }
      },
      "BuildingsCollection": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Building"
            }
          },
          "meta": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ResponseMeta"
              }
            ],
            "required": [
              "pagination"
            ]
          }
        }
      },
      "PlacesCollection": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CampusPlace"
            }
          },
          "meta": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ResponseMeta"
              }
            ],
            "required": [
              "pagination"
            ]
          }
        }
      },
      "BuildingResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Building"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        }
      },
      "PlaceResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CampusPlace"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        }
      },
      "RouteResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Route"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        }
      },
      "GapPlanResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/GapPlan"
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        }
      },
      "LegacyCampusPlace": {
        "type": "object",
        "required": [
          "id",
          "name",
          "kind",
          "buildingCode",
          "summary",
          "amenities",
          "hoursProvenance",
          "metadataProvenance"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "dining",
              "study",
              "library",
              "service",
              "recreation",
              "amenity",
              "facility"
            ]
          },
          "buildingCode": {
            "type": "string"
          },
          "floorOrRoom": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hoursProvenance": {
            "$ref": "#/components/schemas/CampusFactProvenance"
          },
          "metadataProvenance": {
            "$ref": "#/components/schemas/CampusFactProvenance"
          }
        }
      }
    },
    "responses": {
      "ApiError": {
        "description": "Structured API error. Platform-level abuse protection may also return HTTP 429; clients must respect Retry-After when present. Gapwise does not claim a globally exact application counter on serverless instances.",
        "headers": {
          "X-Request-Id": {
            "description": "Opaque request identifier for support and diagnostics.",
            "schema": {
              "type": "string"
            }
          },
          "Cache-Control": {
            "description": "Response caching policy.",
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "description": "Seconds to wait when supplied with HTTP 429.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "code": "invalid_query",
                "message": "limit must be an integer from 1 to 100."
              },
              "meta": {
                "apiVersion": "v1",
                "requestId": "iad1::example"
              }
            }
          }
        }
      },
      "LegacyApiError": {
        "description": "Legacy flat error response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      }
    }
  },
  "x-data-license-note": "Gapwise source code is MIT. Public responses contain source-provenance metadata and may include OpenStreetMap-derived records subject to OpenStreetMap attribution and the ODbL. The MIT license does not override upstream data licenses."
}
