openapi: 3.0.3 info: title: stratoflights-predictor API version: "1.0.0" description: | Balloon trajectory prediction and wind-dataset management. Three prediction surfaces are exposed: * **`GET /api/v1/prediction`** — Tawhiri-compatible, drop-in for the Cambridge University Spaceflight predictor. * **`POST /api/v2/prediction`** — profile-driven synchronous prediction (arbitrary chains of propagators with constraints). * **`POST /api/v1/predictions`** — the same profile API run asynchronously via a worker pool, polled by job id. Dataset management (download, list, delete, job status) lives under `/api/v1/admin/`, and wind-field visualization data (leaflet-velocity / wind-layer format) under `/api/v1/wind/`. servers: - url: / description: This server. tags: - name: Prediction - name: Datasets - name: Wind - name: Health paths: /ready: get: tags: [Health] summary: Readiness check operationId: readinessCheck responses: "200": description: Readiness status content: application/json: schema: $ref: "#/components/schemas/ReadinessResponse" default: $ref: "#/components/responses/DefaultError" /api/v1/prediction: get: tags: [Prediction] summary: Tawhiri-compatible prediction operationId: performPrediction parameters: - { in: query, name: launch_latitude, required: true, schema: { type: number } } - { in: query, name: launch_longitude, required: true, schema: { type: number } } - { in: query, name: launch_datetime, required: true, schema: { type: string, format: date-time } } - { in: query, name: launch_altitude, schema: { type: number } } - { in: query, name: profile, schema: { type: string, enum: [standard_profile, float_profile], default: standard_profile } } - { in: query, name: ascent_rate, schema: { type: number } } - { in: query, name: burst_altitude, schema: { type: number } } - { in: query, name: descent_rate, schema: { type: number } } - { in: query, name: float_altitude, schema: { type: number } } - { in: query, name: stop_datetime, schema: { type: string, format: date-time } } - { in: query, name: dataset, schema: { type: string, format: date-time } } responses: "200": description: Prediction response content: application/json: schema: $ref: "#/components/schemas/PredictionResponse" default: $ref: "#/components/responses/DefaultError" /api/v2/prediction: post: tags: [Prediction] summary: Profile-driven prediction (synchronous) operationId: performPredictionV2 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PredictionV2Request" responses: "200": description: Prediction result content: application/json: schema: $ref: "#/components/schemas/PredictionV2Response" default: $ref: "#/components/responses/DefaultError" /api/v1/predictions: post: tags: [Prediction] summary: Enqueue an asynchronous prediction operationId: createPredictionJob requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PredictionV2Request" responses: "202": description: Job accepted content: application/json: schema: $ref: "#/components/schemas/PredictionJob" default: $ref: "#/components/responses/DefaultError" /api/v1/predictions/{id}: get: tags: [Prediction] summary: Poll an asynchronous prediction job operationId: getPredictionJob parameters: - { in: path, name: id, required: true, schema: { type: string } } responses: "200": description: Job status (with result when complete) content: application/json: schema: $ref: "#/components/schemas/PredictionJob" default: $ref: "#/components/responses/DefaultError" delete: tags: [Prediction] summary: Cancel a queued prediction job operationId: cancelPredictionJob parameters: - { in: path, name: id, required: true, schema: { type: string } } responses: "204": description: Cancelled default: $ref: "#/components/responses/DefaultError" /api/v1/admin/datasets: get: tags: [Datasets] summary: List stored datasets operationId: listDatasets responses: "200": description: Stored datasets content: application/json: schema: $ref: "#/components/schemas/DatasetList" default: $ref: "#/components/responses/DefaultError" post: tags: [Datasets] summary: Trigger a dataset download operationId: triggerDatasetDownload requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DownloadRequest" responses: "202": description: Download accepted content: application/json: schema: $ref: "#/components/schemas/DownloadAccepted" default: $ref: "#/components/responses/DefaultError" /api/v1/admin/datasets/{name}: delete: tags: [Datasets] summary: Delete a stored dataset by filename operationId: deleteDataset parameters: - { in: path, name: name, required: true, schema: { type: string } } responses: "204": description: Deleted default: $ref: "#/components/responses/DefaultError" /api/v1/admin/jobs: get: tags: [Datasets] summary: List dataset download jobs operationId: listDatasetJobs responses: "200": description: Download jobs content: application/json: schema: type: array items: $ref: "#/components/schemas/DownloadJob" default: $ref: "#/components/responses/DefaultError" /api/v1/admin/jobs/{id}: get: tags: [Datasets] summary: Get a dataset download job operationId: getDatasetJob parameters: - { in: path, name: id, required: true, schema: { type: string } } responses: "200": description: Download job content: application/json: schema: $ref: "#/components/schemas/DownloadJob" default: $ref: "#/components/responses/DefaultError" delete: tags: [Datasets] summary: Cancel a running download job operationId: cancelDatasetJob parameters: - { in: path, name: id, required: true, schema: { type: string } } responses: "204": description: Cancelled default: $ref: "#/components/responses/DefaultError" /api/v1/admin/status: get: tags: [Datasets] summary: Service status summary operationId: getServiceStatus responses: "200": description: Status content: application/json: schema: $ref: "#/components/schemas/StatusResponse" default: $ref: "#/components/responses/DefaultError" /api/v1/wind/meta: get: tags: [Wind] summary: Wind-field visualization metadata operationId: getWindMeta responses: "200": description: Metadata describing the active dataset for visualization content: application/json: schema: $ref: "#/components/schemas/WindMeta" default: $ref: "#/components/responses/DefaultError" /api/v1/wind/field: get: tags: [Wind] summary: Wind-field velocity grid (leaflet-velocity / wind-layer format) operationId: getWindField parameters: - { in: query, name: time, schema: { type: string, format: date-time } } - { in: query, name: altitude, schema: { type: number } } - { in: query, name: min_lat, schema: { type: number } } - { in: query, name: max_lat, schema: { type: number } } - { in: query, name: min_lng, schema: { type: number } } - { in: query, name: max_lng, schema: { type: number } } - { in: query, name: step, schema: { type: number } } responses: "200": description: Two-component (U, V) velocity grid content: application/json: schema: type: array items: $ref: "#/components/schemas/WindComponent" default: $ref: "#/components/responses/DefaultError" components: responses: DefaultError: description: Error content: application/json: schema: $ref: "#/components/schemas/Error" schemas: Error: type: object required: [error] properties: error: type: object required: [type, description] properties: type: { type: string } description: { type: string } ReadinessResponse: type: object required: [status] properties: status: { type: string, enum: [ok, not_ready, error] } dataset_time: { type: string, format: date-time } error_message: { type: string } # --- Tawhiri v1 --------------------------------------------------------- PredictionResponse: type: object required: [prediction, metadata] properties: request: type: object properties: dataset: { type: string } launch_latitude: { type: number } launch_longitude: { type: number } launch_datetime: { type: string } launch_altitude: { type: number } profile: { type: string } ascent_rate: { type: number } burst_altitude: { type: number } descent_rate: { type: number } prediction: type: array items: type: object required: [stage, trajectory] properties: stage: { type: string, enum: [ascent, descent, float] } trajectory: type: array items: $ref: "#/components/schemas/TawhiriPoint" metadata: type: object required: [start_datetime, complete_datetime] properties: start_datetime: { type: string, format: date-time } complete_datetime: { type: string, format: date-time } warnings: type: object additionalProperties: true TawhiriPoint: type: object required: [datetime, latitude, longitude, altitude] properties: datetime: { type: string, format: date-time } latitude: { type: number } longitude: { type: number } altitude: { type: number } # --- v2 profile-driven -------------------------------------------------- PredictionV2Request: type: object required: [launch, profile] description: | A profile-driven prediction. `profile` is an ordered chain of propagators; each integrates from where the previous ended. A stage's `constraints` decide when it ends and what happens next: stop the profile, hand off to `fallback_index`, or clip to the boundary. properties: launch: { $ref: "#/components/schemas/Launch" } direction: type: string enum: [forward, reverse] default: forward description: forward integrates launch→landing; reverse integrates backward in time. profile: type: array items: { $ref: "#/components/schemas/StageSpec" } globals: type: array description: constraints evaluated on every stage in addition to its own. items: { $ref: "#/components/schemas/ConstraintSpec" } options: { $ref: "#/components/schemas/Options" } example: launch: { time: "2026-03-28T12:00:00Z", latitude: 52.2, longitude: 0.1, altitude: 0 } profile: - name: ascent model: { type: constant_rate, rate: 5, include_wind: true } constraints: [{ type: altitude, op: ">=", limit: 30000 }] - name: descent model: { type: parachute_descent, sea_level_rate: 5, include_wind: true } constraints: [{ type: terrain_contact }] Launch: type: object required: [time, latitude, longitude] properties: time: { type: string, format: date-time } latitude: { type: number } longitude: { type: number } altitude: { type: number } StageSpec: type: object required: [name, model] properties: name: { type: string } model: { $ref: "#/components/schemas/ModelSpec" } constraints: type: array items: { $ref: "#/components/schemas/ConstraintSpec" } fallback_index: { type: integer } ModelSpec: type: object required: [type] properties: type: { type: string, enum: [constant_rate, parachute_descent, piecewise, wind] } rate: { type: number } sea_level_rate: { type: number } include_wind: { type: boolean } segments: type: array items: { $ref: "#/components/schemas/PiecewiseSegment" } PiecewiseSegment: type: object required: [until, rate] properties: until: { type: number } rate: { type: number } reference: { type: string, enum: [absolute, profile_start, propagator_start], default: absolute } ConstraintSpec: type: object required: [type] properties: type: { type: string, enum: [altitude, time, terrain_contact, polygon] } op: { type: string, enum: ["<", "<=", ">", ">=", "=="] } limit: { type: number } action: { type: string, enum: [stop, fallback, clip], default: stop } mode: { type: string, enum: [inside, outside] } label: { type: string } vertices: type: array items: { $ref: "#/components/schemas/PolygonVertex" } PolygonVertex: type: object required: [lat, lng] properties: lat: { type: number } lng: { type: number } Options: type: object properties: step_seconds: { type: number } tolerance: { type: number } PredictionV2Response: type: object required: [stages, dataset, started_at, completed_at] properties: stages: type: array items: { $ref: "#/components/schemas/StageResult" } events: type: array items: { $ref: "#/components/schemas/EventSummary" } dataset: { $ref: "#/components/schemas/DatasetInfo" } started_at: { type: string, format: date-time } completed_at: { type: string, format: date-time } StageResult: type: object required: [name, outcome, trajectory] properties: name: { type: string } outcome: { type: string, enum: [stopped, fallback, continued] } constraint: { type: string } termination: { $ref: "#/components/schemas/TerminationInfo" } events: type: array items: { $ref: "#/components/schemas/EventSummary" } trajectory: type: array items: { $ref: "#/components/schemas/TrajectoryPoint" } TrajectoryPoint: type: object required: [time, latitude, longitude, altitude] properties: time: { type: string, format: date-time } latitude: { type: number } longitude: { type: number } altitude: { type: number } GeoState: type: object required: [lat, lng, altitude] properties: lat: { type: number } lng: { type: number } altitude: { type: number } TerminationInfo: type: object required: [violation_time, violation_state, refined_time, refined_state] properties: violation_time: { type: string, format: date-time } violation_state: { $ref: "#/components/schemas/GeoState" } refined_time: { type: string, format: date-time } refined_state: { $ref: "#/components/schemas/GeoState" } EventSummary: type: object required: [type, count] properties: type: { type: string } count: { type: integer, format: int64 } first_time: { type: number } last_time: { type: number } first_state: { $ref: "#/components/schemas/GeoState" } last_state: { $ref: "#/components/schemas/GeoState" } message: { type: string } DatasetInfo: type: object required: [source, epoch] properties: source: { type: string } epoch: { type: string, format: date-time } # --- async jobs --------------------------------------------------------- PredictionJob: type: object required: [id, status, created_at] properties: id: { type: string } status: { type: string, enum: [pending, running, complete, failed, cancelled] } created_at: { type: string, format: date-time } started_at: { type: string, format: date-time } completed_at: { type: string, format: date-time } error: { type: string } result: { $ref: "#/components/schemas/PredictionV2Response" } # --- dataset admin ------------------------------------------------------ Region: type: object required: [min_lat, max_lat, min_lng, max_lng] properties: min_lat: { type: number } max_lat: { type: number } min_lng: { type: number } max_lng: { type: number } HourRange: type: object required: [min_hour, max_hour] properties: min_hour: { type: integer } max_hour: { type: integer } SubsetSpec: type: object properties: region: { $ref: "#/components/schemas/Region" } hour_range: { $ref: "#/components/schemas/HourRange" } members: type: array items: { type: integer } Coverage: type: object required: [region, start_time, end_time] properties: region: { $ref: "#/components/schemas/Region" } start_time: { type: string, format: date-time } end_time: { type: string, format: date-time } DownloadRequest: type: object properties: epoch: { type: string, format: date-time } latest: { type: boolean } subset: { $ref: "#/components/schemas/SubsetSpec" } DownloadAccepted: type: object required: [job_id] properties: job_id: { type: string } DatasetEntry: type: object required: [filename, epoch, loaded] properties: filename: { type: string } epoch: { type: string, format: date-time } subset: { $ref: "#/components/schemas/SubsetSpec" } coverage: { $ref: "#/components/schemas/Coverage" } loaded: { type: boolean } DatasetList: type: object required: [source, datasets] properties: source: { type: string } datasets: type: array items: { $ref: "#/components/schemas/DatasetEntry" } DownloadJob: type: object required: [id, source, dataset, epoch, status, started_at, total_units, done_units, bytes] properties: id: { type: string } source: { type: string } dataset: { type: string } epoch: { type: string, format: date-time } status: { type: string, enum: [pending, running, complete, failed, cancelled] } started_at: { type: string, format: date-time } ended_at: { type: string, format: date-time } error: { type: string } total_units: { type: integer } done_units: { type: integer } bytes: { type: integer, format: int64 } StatusResponse: type: object required: [source, uptime, goroutines, memory_mb, jobs_by_status, stored_datasets, loaded_datasets] properties: source: { type: string } uptime: { type: string } goroutines: { type: integer } memory_mb: { type: integer, format: int64 } jobs_by_status: type: object additionalProperties: { type: integer } stored_datasets: { type: integer } loaded_datasets: { type: integer } # --- wind visualization ------------------------------------------------- WindMeta: type: object required: [source, epoch, default_step, min_step, suggested_altitudes, bbox] properties: source: { type: string } epoch: { type: string, format: date-time } default_step: { type: number } min_step: { type: number } suggested_altitudes: type: array items: { type: integer } bbox: { $ref: "#/components/schemas/Region" } WindComponent: type: object required: [header, data] properties: header: { $ref: "#/components/schemas/WindHeader" } data: type: array items: { type: number } WindHeader: type: object required: [parameterCategory, parameterNumber, nx, ny, lo1, la1, lo2, la2, dx, dy, refTime, forecastTime] properties: parameterCategory: { type: integer } parameterNumber: { type: integer } parameterNumberName: { type: string } parameterUnit: { type: string } nx: { type: integer } ny: { type: integer } lo1: { type: number } la1: { type: number } lo2: { type: number } la2: { type: number } dx: { type: number } dy: { type: number } refTime: { type: string } forecastTime: { type: integer }