openapi: 3.1.0 info: title: Fleet Ops API version: 0.1.0 description: >- Contract baseline for the Fleet Ops demo. "Fleet Ops" is the visible product name; "mobilityops" remains the technical identifier for the repository, deployment directory, database, and internal service/health identifiers only. servers: - url: http://localhost:8128 paths: /health: get: operationId: health responses: '200': description: Healthy content: application/json: schema: $ref: '#/components/schemas/Health' /api/v1/dashboard: get: operationId: getDashboard responses: '200': description: Persisted operational summary content: application/json: schema: $ref: '#/components/schemas/Dashboard' /api/v1/vehicles: get: operationId: listVehicles parameters: - in: query name: status schema: type: string - in: query name: attention_only schema: type: boolean default: false responses: '200': description: Vehicle list /api/v1/vehicles/{public_ref}: get: operationId: getVehicle parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: Vehicle detail '404': $ref: '#/components/responses/NotFound' /api/v1/bookings: get: operationId: listBookings responses: '200': description: Booking list /api/v1/bookings/{public_ref}: get: operationId: getBooking parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: Booking detail /api/v1/bookings/{public_ref}/return-preview: post: operationId: previewVehicleReturn description: >- Non-mutating evaluation of what committing this return would do. Shares its domain evaluation with the commit endpoint below so the two can never drift. No writes, no audit event, no outbox event. parameters: - $ref: '#/components/parameters/PublicRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterReturnRequest' responses: '200': description: Authoritative evaluation of the resulting fleet state '409': description: Booking is not active /api/v1/bookings/{public_ref}/return: post: operationId: registerVehicleReturn parameters: - $ref: '#/components/parameters/PublicRef' - in: header name: Idempotency-Key required: true schema: type: string minLength: 8 maxLength: 128 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterReturnRequest' responses: '200': description: Existing idempotent result replayed '201': description: Return registered '409': description: State or concurrency conflict /api/v1/data-quality/issues: get: operationId: listDataQualityIssues description: Operations Manager only. responses: '200': description: Quality issues /api/v1/data-quality/scan: post: operationId: runDataQualityScan description: Manual trigger for the deterministic five-rule scan. Operations Manager only. responses: '200': description: Counts of newly created issues per rule type /api/v1/data-quality/issues/{public_ref}/merge-customers: post: operationId: mergeDuplicateCustomers parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: Merge completed '409': description: Issue no longer mergeable /api/v1/data-quality/issues/{public_ref}/provide-fields: post: operationId: provideMissingFields description: >- missing_required_field only. Resolves once nothing required remains missing; otherwise leaves the issue open with updated evidence. parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: Issue after the update (may still be open) '409': description: Wrong rule type or issue not open '422': description: Disallowed field or empty value /api/v1/data-quality/issues/{public_ref}/resolve-odometer-regression: post: operationId: resolveOdometerRegression description: >- odometer_regression only. Either retains the canonical odometer, or corrects a related booking's reading -- a correction below the current canonical value is rejected, since it would not resolve the regression. parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: Issue resolved '409': description: Wrong rule type or issue not open '422': description: Invalid booking reference or a correction below canonical /api/v1/data-quality/issues/{public_ref}/resolve-overlap: post: operationId: resolveBookingOverlap description: >- booking_overlap only. Blocks one of the two overlapping bookings and re-verifies no overlap remains before resolving. parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: Issue resolved '409': description: Wrong rule type, issue not open, or overlap still present '422': description: booking_ref not one of the overlapping bookings /api/v1/data-quality/issues/{public_ref}/status-recommendation: post: operationId: previewVehicleStatusRecommendation description: >- vehicle_status_conflict only. Non-mutating: computes the recommendation from the same shared evaluator the scanner and apply endpoint use (app.services.vehicle_status.evaluate_vehicle_status), without resolving the issue, writing an audit event, or queuing automation. Safe to call repeatedly -- see docs/fleet-ops-correction/vehicle-status-decision-table.md. parameters: - $ref: '#/components/parameters/PublicRef' responses: '200': description: >- Current/recommended status, recommendation code, safe_to_apply, manual_review_required, the underlying facts, and a recommendation_token the apply endpoint revalidates against. '409': description: Wrong rule type, issue not open, or vehicle not found /api/v1/data-quality/issues/{public_ref}/apply-recommended-status: post: operationId: applyRecommendedVehicleStatus description: >- vehicle_status_conflict only. Applies the one authoritative recommendation function's output within one transaction: locks the issue and vehicle, recomputes the recommendation from fresh facts, rejects the request if the supplied recommendation_token no longer matches (RECOMMENDATION_STALE), refuses an unsafe/manual-review recommendation (MANUAL_REVIEW_REQUIRED) or a recommendation with nothing to apply (NO_CONFLICT_DETECTED), then re-validates the same evaluator post-write before resolving the issue. parameters: - $ref: '#/components/parameters/PublicRef' requestBody: required: true content: application/json: schema: type: object required: [recommendation_token] properties: recommendation_token: type: string description: The token from the most recent status-recommendation preview call. responses: '200': description: Applied status, reason code and the resolved issue '409': description: >- Wrong rule type, issue not open, vehicle not found, stale recommendation token, manual review required, no conflict detected, or the applied status did not resolve the conflict on re-validation /api/v1/search: get: operationId: search description: >- Bounded typed results (vehicle, booking, data_quality_issue, section). Data-quality and manager-only sections are filtered server-side by role. Customers are never returned -- no customer detail route exists. Every result's `label` is a stable public_ref/section id (never translatable prose); `detail_code` (+ optional `detail_params` for data values like make/model/location) is what the frontend localizes -- the backend never emits English/Dutch/French sentences here. parameters: - in: query name: q required: true schema: type: string minLength: 1 maxLength: 100 responses: '200': description: Search results /api/v1/integrations/status: get: operationId: getIntegrationStatus description: >- Truthful aggregate n8n state derived from outbox delivery counts (not just the most recent event), plus the actual MCP Hub registration_enabled setting. Operations Manager only. responses: '200': description: n8n and MCP Hub integration status /api/v1/integrations/n8n/scheduled-scan: post: operationId: n8nScheduledScan description: >- Triggered by the scheduled n8n quality-scan workflow. Runs the same run_scan() the manual UI action uses; idempotent by construction. security: - serviceToken: [] responses: '200': description: Counts of newly created issues per rule type '401': description: Invalid service token /api/v1/knowledge/questions: post: operationId: askKnowledgeQuestion requestBody: required: true content: application/json: schema: type: object required: [question] properties: question: type: string minLength: 3 maxLength: 1000 responses: '200': description: Grounded, insufficient or unavailable answer content: application/json: schema: $ref: '#/components/schemas/GroundedAnswer' /api/v1/integrations/mcp/operations-summary: get: operationId: mcpOperationsSummary security: - serviceToken: [] responses: '200': description: Read-only operational summary components: securitySchemes: serviceToken: type: http scheme: bearer parameters: PublicRef: in: path name: public_ref required: true schema: type: string responses: NotFound: description: Not found schemas: Health: type: object required: [status, service] properties: status: const: ok service: const: mobilityops-api Dashboard: type: object required: [metrics, attention_items] properties: metrics: type: object additionalProperties: type: integer attention_items: type: array items: type: object RegisterReturnRequest: type: object required: [end_odometer_km, fuel_level_percent, cleanliness_ok, damage_reported, technical_warning] properties: end_odometer_km: type: integer minimum: 0 fuel_level_percent: type: integer minimum: 0 maximum: 100 cleanliness_ok: type: boolean damage_reported: type: boolean technical_warning: type: boolean notes: type: string maxLength: 2000 GroundedAnswer: type: object required: [answer, evidence_state, sources, provider, correlation_id] properties: answer: type: string evidence_state: enum: [grounded, insufficient, unavailable] sources: type: array items: type: object required: [document_id, title, version, section, excerpt] properties: document_id: {type: string} title: {type: string} version: {type: string} section: {type: string} excerpt: {type: string} provider: type: string correlation_id: type: string