openapi: 3.1.0
info:
  title: Iricity Space Stack API
  version: 0.5.0
  description: API-first platform infrastructure for universal space-oriented event ingestion and processing.
servers:
  - url: https://iricity.space
    description: Production space stack API
  - url: http://localhost:3000
    description: Local development server
security:
  - ApiKeyAuth: []
paths:
  /space/health:
    get:
      summary: Health check
      security: []
      responses:
        "200":
          description: Service health status
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  version:
                    type: string
                  status:
                    type: string
                  timestamp:
                    type: string
                    format: date-time
                required: [name, version, status, timestamp]

  /space/sources:
    get:
      summary: List active data sources
      security: []
      responses:
        "200":
          description: Source catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  sources:
                    type: array
                    items:
                      $ref: "#/components/schemas/Source"
                required: [sources]

  /space/sources/capabilities:
    get:
      summary: List connector capability metadata
      security: []
      responses:
        "200":
          description: Capability map by source id
          content:
            application/json:
              schema:
                type: object
                properties:
                  capabilities:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: string
                required: [capabilities]

  /space-event:
    get:
      summary: Canonical event contract
      security: []
      responses:
        "200":
          description: Canonical event contract metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint:
                    type: string
                  purpose:
                    type: string
                  canonical_event:
                    type: array
                    items:
                      type: string
                  costs:
                    type: object
                    properties:
                      event:
                        type: integer
                    required: [event]
                required: [endpoint, purpose, canonical_event, costs]

    post:
      summary: Ingest from source or payload into canonical event shape
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SpaceEventRequest"
      responses:
        "201":
          description: Canonical event created
          content:
            application/json:
              schema:
                type: object
                properties:
                  event:
                    $ref: "#/components/schemas/Event"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [event, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /v1/space-event:
    get:
      summary: Canonical event contract
      security: []
      responses:
        "200":
          description: Canonical event contract metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint:
                    type: string
                  purpose:
                    type: string
                  canonical_event:
                    type: array
                    items:
                      type: string
                  costs:
                    type: object
                    properties:
                      event:
                        type: integer
                    required: [event]
                required: [endpoint, purpose, canonical_event, costs]

    post:
      summary: Ingest from source or payload into canonical event shape
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SpaceEventRequest"
      responses:
        "201":
          description: Canonical event created
          content:
            application/json:
              schema:
                type: object
                properties:
                  event:
                    $ref: "#/components/schemas/Event"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [event, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/events:
    post:
      summary: Create event from source or direct payload
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventRequest"
      responses:
        "201":
          description: Event created
          content:
            application/json:
              schema:
                type: object
                properties:
                  event:
                    $ref: "#/components/schemas/Event"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [event, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"
    get:
      summary: List events
      responses:
        "200":
          description: Event list
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/Event"
                required: [events]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/events/{id}:
    get:
      summary: Get event by id
      parameters:
        - $ref: "#/components/parameters/EventId"
      responses:
        "200":
          description: Event
          content:
            application/json:
              schema:
                type: object
                properties:
                  event:
                    $ref: "#/components/schemas/Event"
                required: [event]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/EventNotFound"

  /space/events/{id}/analyze:
    post:
      summary: Analyze event
      parameters:
        - $ref: "#/components/parameters/EventId"
      responses:
        "201":
          description: Analysis result
          content:
            application/json:
              schema:
                type: object
                properties:
                  analysis:
                    $ref: "#/components/schemas/Analysis"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [analysis, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/EventNotFound"

  /space/analyses:
    get:
      summary: List analyses
      responses:
        "200":
          description: Analysis list
          content:
            application/json:
              schema:
                type: object
                properties:
                  analyses:
                    type: array
                    items:
                      $ref: "#/components/schemas/Analysis"
                required: [analyses]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/orbital/simulate:
    post:
      summary: Simulate orbital decision for an event
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                event_id:
                  type: string
              required: [event_id]
      responses:
        "200":
          description: Simulation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  simulation:
                    $ref: "#/components/schemas/Simulation"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [simulation, usage]
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/EventNotFound"

  /space/events/{id}/receipt:
    post:
      summary: Create receipt for an event
      parameters:
        - $ref: "#/components/parameters/EventId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                action:
                  type: string
      responses:
        "201":
          description: Receipt issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  receipt:
                    $ref: "#/components/schemas/Receipt"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [receipt, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/EventNotFound"

  /space/receipts:
    get:
      summary: List receipts
      responses:
        "200":
          description: Receipt list
          content:
            application/json:
              schema:
                type: object
                properties:
                  receipts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Receipt"
                required: [receipts]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/pipeline/run:
    post:
      summary: Run unified source pipeline
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PipelineRunRequest"
      responses:
        "201":
          description: Pipeline run completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  run:
                    $ref: "#/components/schemas/PipelineRun"
                  event:
                    $ref: "#/components/schemas/Event"
                  analysis:
                    $ref: "#/components/schemas/Analysis"
                  simulation:
                    anyOf:
                      - $ref: "#/components/schemas/Simulation"
                      - type: "null"
                  receipt:
                    $ref: "#/components/schemas/Receipt"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [run, event, analysis, receipt, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/pipeline/dry-run:
    post:
      summary: Run pipeline in preview mode without persistence or usage deduction
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PipelineRunRequest"
      responses:
        "200":
          description: Dry-run preview output
          content:
            application/json:
              schema:
                type: object
                properties:
                  mode:
                    type: string
                  persisted:
                    type: boolean
                  usage_consumed:
                    type: integer
                  estimated_usage_units:
                    type: integer
                  source:
                    type: string
                  policy:
                    type: object
                    additionalProperties: true
                  event:
                    $ref: "#/components/schemas/Event"
                  analysis:
                    $ref: "#/components/schemas/Analysis"
                  simulation:
                    anyOf:
                      - $ref: "#/components/schemas/Simulation"
                      - type: "null"
                  receipt_preview:
                    $ref: "#/components/schemas/Receipt"
                required: [mode, persisted, usage_consumed, estimated_usage_units, source, event, analysis, receipt_preview]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/pipeline/runs:
    get:
      summary: List pipeline runs
      parameters:
        - name: source
          in: query
          required: false
          schema:
            type: string
            enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        - name: limit
          in: query
          required: false
          description: Max runs to return (1-200). Defaults to 50.
          schema:
            type: integer
            minimum: 1
            maximum: 200
      responses:
        "200":
          description: Pipeline run history
          content:
            application/json:
              schema:
                type: object
                properties:
                  runs:
                    type: array
                    items:
                      $ref: "#/components/schemas/PipelineRun"
                required: [runs]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/usage:
    get:
      summary: Get usage summary for API key
      responses:
        "200":
          description: Usage summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  usage:
                    $ref: "#/components/schemas/UsageSummary"
                required: [usage]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/usage/ledger:
    get:
      summary: List usage ledger entries
      parameters:
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 500
      responses:
        "200":
          description: Usage ledger rows
          content:
            application/json:
              schema:
                type: object
                properties:
                  ledger:
                    type: array
                    items:
                      $ref: "#/components/schemas/UsageLedgerRow"
                required: [ledger]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/usage/summary:
    get:
      summary: Get usage summary in optional date range
      parameters:
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Usage summary in date window
          content:
            application/json:
              schema:
                type: object
                properties:
                  usage:
                    $ref: "#/components/schemas/UsageWindowSummary"
                required: [usage]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/keys:
    get:
      summary: List API keys (admin)
      security:
        - AdminKeyAuth: []
      responses:
        "200":
          description: API keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/AdminKeyRecord"
                required: [keys]
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      summary: Create API key (admin)
      security:
        - AdminKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                key:
                  type: string
      responses:
        "201":
          description: Key created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminKeyCreateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/keys/{key}/revoke:
    post:
      summary: Revoke API key (admin)
      security:
        - AdminKeyAuth: []
      parameters:
        - name: key
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Key revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminKeyRevokeResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Key not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /space/status:
    get:
      summary: Runtime and persistence status (admin)
      security:
        - AdminKeyAuth: []
      responses:
        "200":
          description: Service status payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/audit:
    get:
      summary: Audit log stream (admin)
      security:
        - AdminKeyAuth: []
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 500
      responses:
        "200":
          description: Audit log entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  audit:
                    type: array
                    items:
                      $ref: "#/components/schemas/AuditEntry"
                required: [audit]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/export-bundle:
    get:
      summary: Export signed bundle of recent events, analyses, receipts, and runs (admin)
      security:
        - AdminKeyAuth: []
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
      responses:
        "200":
          description: Signed export bundle
          content:
            application/json:
              schema:
                type: object
                properties:
                  bundle:
                    $ref: "#/components/schemas/ExportBundle"
                required: [bundle]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /receipts/verify:
    post:
      summary: Verify a receipt hash and optional chain links
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                receipt_id:
                  type: string
                receipt_hash:
                  type: string
                verify_chain:
                  type: boolean
                  default: true
              required: [receipt_id]
      responses:
        "200":
          description: Receipt verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                  receipt_id:
                    type: string
                  action:
                    type: string
                  errors:
                    type: array
                    items:
                      type: string
                  checks:
                    type: array
                    items:
                      type: string
                required: [verified, receipt_id, action, errors]
        "400":
          description: Missing or malformed request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Receipt not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /v1/pipeline/run:
    post:
      summary: Run unified source pipeline
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PipelineRunRequest"
      responses:
        "201":
          description: Pipeline run completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  run:
                    $ref: "#/components/schemas/PipelineRun"
                  event:
                    $ref: "#/components/schemas/Event"
                  analysis:
                    $ref: "#/components/schemas/Analysis"
                  simulation:
                    anyOf:
                      - $ref: "#/components/schemas/Simulation"
                      - type: "null"
                  receipt:
                    $ref: "#/components/schemas/Receipt"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [run, event, analysis, receipt, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /v1/events:
    post:
      summary: Create event from source or direct payload
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventRequest"
      responses:
        "201":
          description: Event created
          content:
            application/json:
              schema:
                type: object
                properties:
                  event:
                    $ref: "#/components/schemas/Event"
                  usage:
                    $ref: "#/components/schemas/UsageConsumption"
                required: [event, usage]
        "401":
          $ref: "#/components/responses/Unauthorized"
    get:
      summary: List events
      responses:
        "200":
          description: Event list
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/Event"
                required: [events]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/source-health:
    get:
      summary: Check health of configured connectors in parallel (admin)
      security:
        - AdminKeyAuth: []
      parameters:
        - name: source
          in: query
          required: false
          schema:
            type: string
            enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        - name: timeout_ms
          in: query
          required: false
          description: Per-source timeout in milliseconds (500-15000). Defaults to 3500.
          schema:
            type: integer
            minimum: 500
            maximum: 15000
      responses:
        "200":
          description: Health results by source
          content:
            application/json:
              schema:
                type: object
                properties:
                  checks:
                    type: array
                    items:
                      $ref: "#/components/schemas/SourceHealthCheck"
                  checked_at:
                    type: string
                    format: date-time
                required: [checks, checked_at]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/metrics:
    get:
      summary: Export structured service metrics snapshot (admin)
      security:
        - AdminKeyAuth: []
      parameters:
        - name: window_minutes
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1440
        - name: sample_limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 10000
      responses:
        "200":
          description: Metrics snapshot payload
          content:
            application/json:
              schema:
                type: object
                properties:
                  metrics:
                    $ref: "#/components/schemas/MetricsSnapshot"
                  generated_at:
                    type: string
                    format: date-time
                required: [metrics, generated_at]
        "401":
          $ref: "#/components/responses/Unauthorized"

  /space/admin/alerts/evaluate:
    post:
      summary: Evaluate alert thresholds and optionally deliver webhook (admin)
      security:
        - AdminKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                window_minutes:
                  type: integer
                  minimum: 1
                  maximum: 1440
                sample_limit:
                  type: integer
                  minimum: 1
                  maximum: 10000
                error_rate_threshold:
                  type: number
                  minimum: 0
                  maximum: 1
                p95_latency_ms_threshold:
                  type: integer
                  minimum: 1
                webhook_url:
                  type: string
      responses:
        "200":
          description: Alert evaluation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  evaluation:
                    $ref: "#/components/schemas/AlertEvaluation"
                  metrics:
                    $ref: "#/components/schemas/MetricsSnapshot"
                  alert_delivery:
                    type: object
                    additionalProperties: true
                  evaluated_at:
                    type: string
                    format: date-time
                required: [evaluation, metrics, alert_delivery, evaluated_at]
        "401":
          $ref: "#/components/responses/Unauthorized"

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
    AdminKeyAuth:
      type: apiKey
      in: header
      name: x-admin-key

  parameters:
    EventId:
      name: id
      in: path
      required: true
      schema:
        type: string

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    EventNotFound:
      description: Event not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
      required: [error]

    Source:
      type: object
      properties:
        id:
          type: string
          enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        status:
          type: string
        mode:
          type: string
        capabilities:
          type: array
          items:
            type: string
      required: [id, status, mode, capabilities]

    CanonicalEvent:
      type: object
      properties:
        event_id:
          type: string
        source:
          type: string
        event_type:
          type: string
        timestamp:
          type: string
          format: date-time
        payload:
          type: object
          additionalProperties: true
        payload_hash:
          type: string
        status:
          type: string
          enum: [processed]
      required: [event_id, source, event_type, timestamp, payload, payload_hash, status]

    Event:
      allOf:
        - $ref: "#/components/schemas/CanonicalEvent"
        - type: object
          properties:
            schema_version:
              type: string
              const: "1.0.0"
          required: [schema_version]

    Analysis:
      type: object
      properties:
        analysis_id:
          type: string
        event_id:
          type: string
        classification:
          type: string
        confidence:
          type: number
        status:
          type: string
        timestamp:
          type: string
          format: date-time
      required: [analysis_id, event_id, classification, confidence, status, timestamp]

    Receipt:
      type: object
      properties:
        receipt_id:
          type: string
        event_id:
          type: string
        action:
          type: string
        payload_hash:
          type: string
        timestamp:
          type: string
          format: date-time
        status:
          type: string
      required: [receipt_id, event_id, action, payload_hash, timestamp, status]

    Simulation:
      type: object
      properties:
        simulation_id:
          type: string
        event_id:
          type: string
        decision:
          type: string
        constraints:
          type: object
          properties:
            payload_size:
              type: integer
            power_budget:
              type: integer
            storage_budget:
              type: integer
            bandwidth_budget:
              type: integer
            power_ok:
              type: boolean
            storage_ok:
              type: boolean
            bandwidth_ok:
              type: boolean
          required: [payload_size, power_budget, storage_budget, bandwidth_budget, power_ok, storage_ok, bandwidth_ok]
        comm_window:
          type: object
          properties:
            has_window:
              type: boolean
            link_latency_ms:
              type: integer
            opens_at:
              type: string
              format: date-time
            closes_at:
              type: string
              format: date-time
          required: [has_window, link_latency_ms, opens_at, closes_at]
        downlink_queue:
          type: object
          properties:
            queue_id:
              type: string
            event_id:
              type: string
            mode:
              type: string
            priority:
              type: string
            enqueued_at:
              type: string
              format: date-time
          required: [queue_id, event_id, mode, priority, enqueued_at]
        uncertainty:
          type: object
          properties:
            sensor_noise:
              type: number
            ephemeris_drift:
              type: number
            atmospheric_jitter:
              type: number
            payload_factor:
              type: number
            confidence:
              type: number
          required: [sensor_noise, ephemeris_drift, atmospheric_jitter, payload_factor, confidence]
        maneuver_plan:
          type: object
          properties:
            plan_id:
              type: string
            steps:
              type: array
              items:
                type: object
                properties:
                  step:
                    type: string
                  reason:
                    type: string
                required: [step, reason]
            estimated_delta_v_mps:
              type: number
            risk_level:
              type: string
          required: [plan_id, steps, estimated_delta_v_mps, risk_level]
        explainability:
          type: object
          properties:
            weighted_score:
              type: number
            selected_decision:
              type: string
            selected_reason:
              type: string
            queue_mode:
              type: string
            recommendation:
              type: string
          required: [weighted_score, selected_decision, selected_reason, queue_mode, recommendation]
        reason:
          type: string
        status:
          type: string
        timestamp:
          type: string
          format: date-time
      required: [simulation_id, event_id, decision, constraints, comm_window, downlink_queue, uncertainty, maneuver_plan, explainability, reason, status, timestamp]

    PipelineRun:
      type: object
      properties:
        run_id:
          type: string
        source:
          type: string
        event_id:
          type: string
        analysis_id:
          type: string
        receipt_id:
          type: string
        simulation_id:
          anyOf:
            - type: string
            - type: "null"
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        status:
          type: string
        created_at:
          type: string
          format: date-time
      required: [run_id, source, event_id, analysis_id, receipt_id, started_at, completed_at, status, created_at]

    CreateEventRequest:
      type: object
      properties:
        source:
          type: string
          default: mock
          enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        event_type:
          type: string
          default: space_observation
        payload:
          type: object
          additionalProperties: true
        options:
          type: object
          additionalProperties: true
      example:
        source: nasa_power
        options:
          latitude: 28.6139
          longitude: 77.209
          start: "20250101"
          end: "20250102"

    SpaceEventRequest:
      type: object
      properties:
        source:
          type: string
          default: mock
          enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        event_type:
          type: string
          default: space_observation
        payload:
          type: object
          additionalProperties: true
        options:
          type: object
          additionalProperties: true
      required: [source]

    PipelineRunRequest:
      type: object
      properties:
        source:
          type: string
          default: mock
          enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        options:
          type: object
          additionalProperties: true
          description: Connector-specific options (e.g., lat/lon for nasa_power, query for bhuvan)
        include_simulation:
          type: boolean
          default: true
      example:
        source: bhuvan
        options:
          query: Bengaluru
        include_simulation: true

    UsageSummary:
      type: object
      properties:
        used:
          type: integer
        unit_type:
          type: string
      required: [used, unit_type]

    UsageConsumption:
      allOf:
        - $ref: "#/components/schemas/UsageSummary"
        - type: object
          properties:
            units:
              type: integer
          required: [units]

    UsageLedgerRow:
      type: object
      properties:
        id:
          type: integer
        api_key:
          type: string
        units:
          type: integer
        unit_type:
          type: string
        created_at:
          type: string
          format: date-time
      required: [id, api_key, units, unit_type, created_at]

    UsageWindowSummary:
      type: object
      properties:
        used:
          type: integer
        entries:
          type: integer
        unit_type:
          type: string
        from:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        to:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
      required: [used, entries, unit_type, from, to]

    AdminKeyRecord:
      type: object
      properties:
        key:
          type: string
        key_masked:
          type: string
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        revoked_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
      required: [key, key_masked, active, created_at, revoked_at]

    AdminKeyCreateResponse:
      type: object
      properties:
        key:
          type: string
        key_masked:
          type: string
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
      required: [key, key_masked, active, created_at]

    AdminKeyRevokeResponse:
      type: object
      properties:
        key_masked:
          type: string
        active:
          type: boolean
        revoked_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
      required: [key_masked, active, revoked_at]

    AdminStatus:
      type: object
      properties:
        name:
          type: string
        version:
          type: string
        status:
          type: string
        uptime_seconds:
          type: integer
        db_ok:
          type: boolean
        api_key_stats:
          type: object
          properties:
            total_keys:
              type: integer
            active_keys:
              type: integer
            revoked_keys:
              type: integer
          required: [total_keys, active_keys, revoked_keys]
        recent_metrics:
          $ref: "#/components/schemas/MetricsSnapshot"
        timestamp:
          type: string
          format: date-time
      required: [name, version, status, uptime_seconds, db_ok, api_key_stats, recent_metrics, timestamp]

    AuditEntry:
      type: object
      properties:
        id:
          type: integer
        request_id:
          type: string
        method:
          type: string
        route:
          type: string
        status_code:
          type: integer
        api_key_hash:
          anyOf:
            - type: string
            - type: "null"
        latency_ms:
          type: integer
        created_at:
          type: string
          format: date-time
      required: [id, request_id, method, route, status_code, latency_ms, created_at]

    ExportBundle:
      type: object
      properties:
        generated_at:
          type: string
          format: date-time
        counts:
          type: object
          properties:
            events:
              type: integer
            analyses:
              type: integer
            receipts:
              type: integer
            runs:
              type: integer
          required: [events, analyses, receipts, runs]
        events:
          type: array
          items:
            $ref: "#/components/schemas/Event"
        analyses:
          type: array
          items:
            $ref: "#/components/schemas/Analysis"
        receipts:
          type: array
          items:
            $ref: "#/components/schemas/Receipt"
        runs:
          type: array
          items:
            $ref: "#/components/schemas/PipelineRun"
        bundle_hash:
          type: string
      required: [generated_at, counts, events, analyses, receipts, runs, bundle_hash]

    SourceHealthCheck:
      type: object
      properties:
        source:
          type: string
          enum: [nasa_apod, nasa_power, bhuvan, iss_orbital, noaa_space_weather, file_ingestion, esa_neo, eumetsat_imagery, mock]
        status:
          type: string
          enum: [ok, error, timeout]
        latency_ms:
          type: integer
      required: [source, status, latency_ms]

    MetricsSnapshot:
      type: object
      properties:
        window_minutes:
          type: integer
        since:
          type: string
          format: date-time
        sampled_requests:
          type: integer
        errors_5xx:
          type: integer
        error_rate:
          type: number
        latency_ms:
          type: object
          properties:
            p50:
              type: integer
            p95:
              type: integer
            max:
              type: integer
          required: [p50, p95, max]
        routes:
          type: array
          items:
            type: object
            properties:
              method:
                type: string
              route:
                type: string
              requests:
                type: integer
              errors_5xx:
                type: integer
            required: [method, route, requests, errors_5xx]
      required: [window_minutes, since, sampled_requests, errors_5xx, error_rate, latency_ms, routes]

    AlertEvaluation:
      type: object
      properties:
        should_alert:
          type: boolean
        triggers:
          type: array
          items:
            type: object
            additionalProperties: true
        thresholds:
          type: object
          properties:
            error_rate:
              type: number
            p95_latency_ms:
              type: number
          required: [error_rate, p95_latency_ms]
      required: [should_alert, triggers, thresholds]
