openapi: 3.1.0 info: title: BikepackPilot API version: 0.1.0 description: MVP API contract for bikepacking route planning, staging, POI corridor, offline packs, exports, and local reports. servers: - url: http://localhost:8000 paths: /v1/trips: post: summary: Create trip requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TripRequest' responses: '201': description: Trip created content: application/json: schema: $ref: '#/components/schemas/Trip' /v1/routes/plan: post: summary: Plan a bikepacking route requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RoutePlanRequest' responses: '200': description: Route plan content: application/json: schema: $ref: '#/components/schemas/RoutePlan' /v1/stages/plan: post: summary: Split a route into daily stages requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/StagePlanRequest' responses: '200': description: Stage plans content: application/json: schema: type: object properties: stages: type: array items: $ref: '#/components/schemas/StagePlan' /v1/routes/{routeId}/pois: get: summary: Get route-corridor POIs parameters: - in: path name: routeId required: true schema: { type: string } - in: query name: category schema: { $ref: '#/components/schemas/PoiCategory' } - in: query name: buffer_km schema: { type: number, default: 5 } responses: '200': description: POIs content: application/json: schema: type: object properties: pois: type: array items: $ref: '#/components/schemas/Poi' attribution: type: array items: { type: string } /v1/routes/{routeId}/gaps: get: summary: Get critical service gaps parameters: - in: path name: routeId required: true schema: { type: string } responses: '200': description: Service gaps content: application/json: schema: type: object properties: gaps: type: array items: $ref: '#/components/schemas/ServiceGap' /v1/rules: get: summary: Get local rule cards parameters: - in: query name: bbox schema: type: string description: minLon,minLat,maxLon,maxLat - in: query name: route_id schema: { type: string } responses: '200': description: Rule cards content: application/json: schema: type: object properties: rules: type: array items: $ref: '#/components/schemas/RuleCard' disclaimer: type: string /v1/offline-packs: post: summary: Create offline pack manifest requestBody: required: true content: application/json: schema: type: object required: [tripId, routeId] properties: tripId: { type: string } routeId: { type: string } routeBufferKm: { type: number, default: 5 } responses: '200': description: Offline pack manifest content: application/json: schema: $ref: '#/components/schemas/OfflinePackManifest' /v1/routes/{routeId}/export: get: summary: Export route parameters: - in: path name: routeId required: true schema: { type: string } - in: query name: format required: true schema: type: string enum: [gpx, tcx, fit] responses: '200': description: Route export file content: application/gpx+xml: schema: { type: string } application/json: schema: { type: object } /v1/reports: post: summary: Submit local report requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LocalReportCreate' responses: '201': description: Report submitted content: application/json: schema: $ref: '#/components/schemas/LocalReport' components: schemas: Coordinate: type: object required: [lat, lon] properties: lat: { type: number } lon: { type: number } elevationM: type: number description: Optional elevation in meters above sea level when supplied by the active provider. TripRequest: type: object required: [start, end, profile, sleepPreference, dailyDistanceKm] properties: start: { $ref: '#/components/schemas/Coordinate' } end: { $ref: '#/components/schemas/Coordinate' } viaPoints: type: array items: { $ref: '#/components/schemas/Coordinate' } maxItems: 24 startDate: { type: string, format: date } endDate: { type: string, format: date } profile: { type: string } sleepPreference: { type: string } dailyDistanceKm: type: object required: [min, target, max] properties: min: { type: number } target: { type: number } max: { type: number } waterGapMaxKm: { type: number } foodGapMaxKm: { type: number } Trip: type: object properties: id: { type: string } title: { type: string } request: { $ref: '#/components/schemas/TripRequest' } RoutePlanRequest: type: object required: [tripRequest] properties: tripRequest: { $ref: '#/components/schemas/TripRequest' } provider: { type: string, default: mock } RoutePlan: type: object properties: id: { type: string } tripId: { type: string } geometry: type: array items: { $ref: '#/components/schemas/Coordinate' } distanceM: { type: integer } ascentM: { type: integer } descentM: { type: integer } provider: { type: string } segments: type: array items: { $ref: '#/components/schemas/RouteSegment' } surfaceBreakdown: type: object additionalProperties: { type: number } suitabilityScore: { type: number } warnings: type: array items: { $ref: '#/components/schemas/RouteWarning' } attribution: type: array items: { type: string } RouteSegment: type: object properties: id: { type: string } geometry: type: array items: { $ref: '#/components/schemas/Coordinate' } distanceM: { type: integer } startMeters: { type: integer } endMeters: { type: integer } ascentM: { type: integer } descentM: { type: integer } avgGradePercent: { type: number } maxGradePercent: { type: number } surface: { type: string } smoothness: { type: string } highway: { type: string } bicycleAccess: { type: string } legalAccessConfidence: { type: string } trafficStress: { type: number } officialCycleRoute: { type: boolean } protectedAreaOverlap: { type: boolean } sourceConfidence: { type: string } StagePlanRequest: type: object required: [routeId, tripRequest] properties: routeId: { type: string } tripRequest: { $ref: '#/components/schemas/TripRequest' } StagePlan: type: object properties: id: { type: string } dayIndex: { type: integer } startMeters: { type: integer } endMeters: { type: integer } distanceM: { type: integer } ascentM: { type: integer } descentM: { type: integer } expectedRideTimeMinutes: type: object properties: low: { type: integer } high: { type: integer } sleepCandidates: type: array items: { $ref: '#/components/schemas/Poi' } waterPois: type: array items: { $ref: '#/components/schemas/Poi' } foodPois: type: array items: { $ref: '#/components/schemas/Poi' } repairPois: type: array items: { $ref: '#/components/schemas/Poi' } bailoutPois: type: array items: { $ref: '#/components/schemas/Poi' } warnings: type: array items: { $ref: '#/components/schemas/RouteWarning' } planB: type: object properties: earlierEndMeters: { type: integer } laterEndMeters: { type: integer } reason: { type: string } PoiCategory: type: string enum: [water, food, sleep, repair, bailout, charging, emergency] Poi: type: object properties: id: { type: string } category: { $ref: '#/components/schemas/PoiCategory' } name: { type: string } location: { $ref: '#/components/schemas/Coordinate' } source: { type: string } confidence: { type: string } metersFromStart: { type: integer } distanceFromRouteM: { type: integer } detourDistanceM: { type: integer } RouteWarning: type: object properties: code: { type: string } severity: { type: string, enum: [info, notice, warning, critical] } title: { type: string } message: { type: string } metersFromStart: { type: integer } segmentId: { type: string } ServiceGap: type: object properties: category: { $ref: '#/components/schemas/PoiCategory' } startM: { type: integer } endM: { type: integer } distanceM: { type: integer } severity: { type: string } explanation: { type: string } RuleCard: type: object properties: id: { type: string } ruleType: { type: string } jurisdiction: { type: string } status: { type: string } confidence: { type: string } summary: { type: string } sourceUrl: { type: string } lastReviewedAt: { type: string, format: date } OfflinePackManifest: type: object properties: packId: { type: string } tripId: { type: string } routeId: { type: string } createdAt: { type: string, format: date-time } expiresAt: { type: string, format: date-time } bbox: type: array minItems: 4 maxItems: 4 items: { type: number } routeBufferKm: { type: number } includedLayers: type: array items: { type: string } dataVersions: { type: object } files: type: array items: { type: object } warnings: type: array items: { $ref: '#/components/schemas/RouteWarning' } LocalReportCreate: type: object required: [reportType, location, observedAt] properties: reportType: { type: string } location: { $ref: '#/components/schemas/Coordinate' } routeId: { type: string } metersFromStart: { type: integer } observedAt: { type: string, format: date-time } details: { type: string } payload: { type: object } offlineClientId: { type: string } LocalReport: allOf: - $ref: '#/components/schemas/LocalReportCreate' - type: object properties: id: { type: string } moderationStatus: { type: string } trustScore: { type: number } queuedOffline: { type: boolean }