openapi: 3.0.3
info:
  title: GeoAlterSense API
  version: 1.0.0
  description: |
    GeoAlterSense task orchestration and geospatial analysis API.
    Most JSON endpoints return the common envelope:
    `{"success": boolean, "message": string, "data": object}`.
servers:
  - url: /

paths:
  /api/:
    get:
      summary: Service health
      operationId: getHealth
      tags: [System]
      responses:
        '200':
          description: Service is running
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                success: true
                message: GeoAlterSense backend is running
                data:
                  service: GeoAlterSense
                  vendor: Arybit Technologies
                  version: 1.0.0
                  time: '2026-03-08T02:54:02Z'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/tasks:
    get:
      summary: List tasks
      operationId: listTasks
      tags: [Tasks]
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - in: query
          name: limit
          description: Number of records per page (min 1, max 100, default 20)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - in: query
          name: page
          description: Page number (min 1, default 1)
          schema:
            type: integer
            minimum: 1
            default: 1
      responses:
        '200':
          description: Tasks retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListTasksResponse'
              example:
                success: true
                message: Tasks retrieved
                data:
                  items:
                    - task_id: 103
                      query: Copper alteration mapping near Kitui
                      status: completed
                      include_temporal: 1
                      include_hyperspectral: 0
                      created_at: '2026-03-07 20:15:03'
                      updated_at: '2026-03-07 20:18:19'
                  page: 1
                  limit: 20
        '500':
          $ref: '#/components/responses/InternalServerError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'

  /api/tasks/create:
    post:
      summary: Create analysis task
      operationId: createTask
      tags: [Tasks]
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTaskRequest'
            examples:
              using_lat_lon:
                value:
                  query: Copper alteration mapping near Kitui
                  latitude: -1.3667
                  longitude: 38.0106
                  sources_config: [landsat_ot_c2_l2]
                  analysis_domains: [mineral_exploration]
                  include_temporal: true
                  include_hyperspectral: false
                  include_forecast: true
              using_bbox:
                value:
                  query: Iron oxide proxy scan
                  aoi_bbox: [37.5, -2.1, 38.2, -1.7]
                  include_temporal: false
                  include_hyperspectral: false
                  include_forecast: false
      responses:
        '202':
          description: Task accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateTaskResponse'
              example:
                success: true
                message: Task accepted
                data:
                  task_id: 104
                  status: queued
                  status_url: /api/tasks/104/status
                  results_url: /api/tasks/104/results
                  map:
                    center: { lat: -1.3667, lon: 38.0106 }
                    bounds: { min_lon: 37.8106, min_lat: -1.5667, max_lon: 38.2106, max_lat: -1.1667 }
                    default_zoom: 11
                    layers:
                      - { name: Landsat, url: /tiles/landsat/104/{z}/{x}/{y}.png }
                      - { name: NDVI, url: /tiles/ndvi/104/{z}/{x}/{y}.png }
                      - { name: Iron Oxide, url: /tiles/iron/104/{z}/{x}/{y}.png }
                      - { name: Clay Ratio, url: /tiles/clay/104/{z}/{x}/{y}.png }
        '400':
          description: Invalid JSON body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Invalid JSON body
                data: {}
        '422':
          description: Validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missing_query:
                  value:
                    success: false
                    message: query is required
                    data: {}
                missing_location:
                  value:
                    success: false
                    message: provide latitude/longitude or aoi_bbox
                    data: {}
        '500':
          $ref: '#/components/responses/InternalServerError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'

  /api/tasks/{taskId}/status:
    get:
      summary: Get task status
      operationId: getTaskStatus
      tags: [Tasks]
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - in: path
          name: taskId
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Task status retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskStatusResponse'
              example:
                success: true
                message: Task status retrieved
                data:
                  task_id: 104
                  status: ingesting
                  progress: 35
                  updated_at: '2026-03-08 02:54:02'
        '404':
          description: Task not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Task not found
                data: {}
        '500':
          $ref: '#/components/responses/InternalServerError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'

  /api/tasks/{taskId}/results:
    get:
      summary: Get task results
      operationId: getTaskResults
      tags: [Tasks]
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - in: path
          name: taskId
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Task results retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskResultsResponse'
              example:
                success: true
                message: Task results retrieved
                data:
                  task:
                    task_id: 104
                    query: Copper alteration mapping near Kitui
                    status: completed
                    analysis_domains: [mineral_exploration]
                    sources_config: [landsat_ot_c2_l2]
                    area_of_interest:
                      type: Polygon
                      coordinates:
                        - [[37.8106, -1.5667], [38.2106, -1.5667], [38.2106, -1.1667], [37.8106, -1.1667], [37.8106, -1.5667]]
                    include_hyperspectral: 0
                    include_temporal: 1
                    include_forecast: 1
                    error_message: null
                    created_at: '2026-03-08 02:54:02'
                    updated_at: '2026-03-08 03:01:18'
                  summary: High-confidence iron-oxide and clay alteration zone in northeast AOI.
                  sources:
                    - Sentinel/Landsat fused indices
                    - Temporal NDVI trend
                  confidence_score: 0.89
                  bias_score: 0.08
                  scenes:
                    - source_id: 17
                      task_id: 104
                      title: LC08_L2SP_168061_20260223
                      raw_content: '{"cloud_cover":9.1}'
                      acquired_date: '2026-02-23'
                      fetched_at: '2026-03-08 02:54:34'
                      scene_id: LC08_L2SP_168061_20260223
                      cloud_cover: 9.1
                      dataset: landsat_ot_c2_l2
                      metadata: {}
                      cog_urls: []
                  analysis_results:
                    - id: 45
                      task_id: 104
                      summary: ndvi
                      key_findings:
                        analysis_type: ndvi
                        scene_id: LC08_L2SP_168061_20260223
                        mean: 0.41
                      confidence_score: 0.82
                      platform: api
                      layer_id: task_104_ndvi_LC08_L2SP_168061_20260223
                      created_at: '2026-03-08 02:59:21'
                  layers:
                    - name: Landsat
                      url: /tiles/landsat/104/{z}/{x}/{y}.png
                    - name: NDVI
                      url: /tiles/ndvi/104/{z}/{x}/{y}.png
                    - name: Iron Oxide
                      url: /tiles/iron/104/{z}/{x}/{y}.png
                    - name: Clay Ratio
                      url: /tiles/clay/104/{z}/{x}/{y}.png
                  map:
                    center: { lat: -1.3667, lon: 38.0106 }
                    bounds: { min_lon: 37.8106, min_lat: -1.5667, max_lon: 38.2106, max_lat: -1.1667 }
                    default_zoom: 11
                    layers:
                      - { name: Landsat, url: /tiles/landsat/104/{z}/{x}/{y}.png }
                      - { name: NDVI, url: /tiles/ndvi/104/{z}/{x}/{y}.png }
                      - { name: Iron Oxide, url: /tiles/iron/104/{z}/{x}/{y}.png }
                      - { name: Clay Ratio, url: /tiles/clay/104/{z}/{x}/{y}.png }
        '202':
          description: Task still processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskProcessingResponse'
              example:
                success: true
                message: Task is still processing
                data:
                  task_id: 104
                  status: temporal_analysis
        '400':
          description: Task failed to complete
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Task not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Task not found
                data: {}
        '500':
          $ref: '#/components/responses/InternalServerError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/InternalServerError'

  /api/alteration/process:
    post:
      summary: Forward processing request to external alteration backend
      description: |
        Proxies a request to the high-performance alteration processing service.
        This is the primary endpoint for complex spectral analysis.
      operationId: alterationProcess
      tags: [Alteration]
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [dataset, bbox]
              properties:
                dataset:
                  type: string
                  example: landsat_ot_c2_l2
                bbox:
                  type: array
                  items:
                    type: number
                  minItems: 4
                  maxItems: 4
                  example: [37.8, -1.5, 38.2, -1.1]
      responses:
        '202':
          description: Job accepted by backend
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericEnvelope'
              example:
                success: true
                message: Processing job created
                data:
                  job_id: proc_1234567890ab
                  status: ACCEPTED
        '400':
          description: Invalid request or payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Payload too large
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/alteration/status/{jobId}:
    get:
      summary: Check status of an external alteration job
      operationId: alterationStatus
      tags: [Alteration]
      security:
        - apiKeyAuth: []
      parameters:
        - in: path
          name: jobId
          required: true
          schema:
            type: string
            pattern: '^[a-z0-9]{12}$'
      responses:
        '200':
          description: Job status and results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericEnvelope'
        '404':
          description: Job not found

  /api/usgs/proxy/{method}:
    post:
      summary: Proxy request to USGS M2M API
      description: |
        Forwards a POST request to the USGS M2M API. 
        Handles session authentication and token caching automatically.
      operationId: usgsProxy
      tags: [Data Sources]
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - in: path
          name: method
          required: true
          schema:
            type: string
          description: The USGS M2M API method (e.g., scene-search).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Payload expected by the USGS M2M API method.
      responses:
        '200':
          description: Response from USGS M2M API
          content:
            application/json:
              schema:
                type: object
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '502':
          description: Bad Gateway (USGS error or network failure)
        '503':
          description: Service Unavailable (Authentication with USGS failed)

  /tiles/landsat/{taskId}/{z}/{x}/{y}.png:
    get:
      summary: Landsat RGB tile
      operationId: getLandsatTile
      tags: [Tiles]
      parameters:
        - in: path
          name: taskId
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/TileZ'
        - $ref: '#/components/parameters/TileX'
        - $ref: '#/components/parameters/TileY'
      responses:
        '200':
          description: PNG tile image
          content:
            image/png:
              schema:
                type: string
                format: binary
        '404':
          description: Tile not found
        '500':
          description: Tile rendering/proxy failure

  /tiles/ndvi/{taskId}/{z}/{x}/{y}.png:
    get:
      summary: NDVI analysis tile
      operationId: getNdviTile
      tags: [Tiles]
      parameters:
        - in: path
          name: taskId
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/TileZ'
        - $ref: '#/components/parameters/TileX'
        - $ref: '#/components/parameters/TileY'
      responses:
        '200':
          description: PNG tile image
          content:
            image/png:
              schema:
                type: string
                format: binary
        '404':
          description: Tile not found
        '500':
          description: Tile rendering/proxy failure

  /tiles/iron/{taskId}/{z}/{x}/{y}.png:
    get:
      summary: Iron oxide analysis tile
      operationId: getIronTile
      tags: [Tiles]
      parameters:
        - in: path
          name: taskId
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/TileZ'
        - $ref: '#/components/parameters/TileX'
        - $ref: '#/components/parameters/TileY'
      responses:
        '200':
          description: PNG tile image
          content:
            image/png:
              schema:
                type: string
                format: binary
        '404':
          description: Tile not found
        '500':
          description: Tile rendering/proxy failure

  /tiles/clay/{taskId}/{z}/{x}/{y}.png:
    get:
      summary: Clay ratio analysis tile
      operationId: getClayTile
      tags: [Tiles]
      parameters:
        - in: path
          name: taskId
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/TileZ'
        - $ref: '#/components/parameters/TileX'
        - $ref: '#/components/parameters/TileY'
      responses:
        '200':
          description: PNG tile image
          content:
            image/png:
              schema:
                type: string
                format: binary
        '404':
          description: Tile not found
        '500':
          description: Tile rendering/proxy failure

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Send access token in `Authorization` header:
        `Authorization: Bearer <token>`.
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Send API key in `X-API-Key` header.

  parameters:
    TileZ:
      in: path
      name: z
      required: true
      schema:
        type: integer
        minimum: 0
      description: Web Mercator zoom level
    TileX:
      in: path
      name: x
      required: true
      schema:
        type: integer
        minimum: 0
      description: Tile X index
    TileY:
      in: path
      name: y
      required: true
      schema:
        type: integer
        minimum: 0
      description: Tile Y index

  responses:
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            message: Internal server error
            data: {}
    UnauthorizedError:
      description: Authentication failed or missing credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            message: Unauthorized
            data: {}
    ForbiddenError:
      description: Authenticated but lacks required permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            message: Forbidden
            data: {}

  schemas:
    GenericEnvelope:
      type: object
      required: [success, message, data]
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
          additionalProperties: true

    ErrorResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        success:
          type: boolean
          enum: [false]
        data:
          type: object
          additionalProperties: true

    HealthResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        data:
          type: object
          required: [service, vendor, version, time]
          properties:
            service:
              type: string
            vendor:
              type: string
            version:
              type: string
            time:
              type: string
              format: date-time

    ListTasksResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        data:
          type: object
          required: [items, page, limit]
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/TaskListItem'
            page:
              type: integer
            limit:
              type: integer

    CreateTaskRequest:
      type: object
      required: [query]
      properties:
        query:
          type: string
          minLength: 1
          description: Human-readable analysis request.
        latitude:
          type: number
          format: float
          minimum: -90
          maximum: 90
          description: Optional center latitude (required with longitude if `aoi_bbox` is not provided).
        longitude:
          type: number
          format: float
          minimum: -180
          maximum: 180
          description: Optional center longitude (required with latitude if `aoi_bbox` is not provided).
        aoi_bbox:
          description: Optional AOI bounding box as [minLon, minLat, maxLon, maxLat].
          oneOf:
            - type: string
              example: '37.5,-2.1,38.2,-1.7'
            - type: array
              minItems: 4
              maxItems: 4
              items:
                type: number
                format: float
        sources_config:
          type: array
          items:
            type: string
          default: [landsat_ot_c2_l2]
        analysis_domains:
          type: array
          items:
            type: string
          default: [mineral_exploration]
        include_temporal:
          type: boolean
          default: false
        include_hyperspectral:
          type: boolean
          default: false
        include_forecast:
          type: boolean
          default: false

    CreateTaskResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        data:
          type: object
          required: [task_id, status, status_url, results_url, map]
          properties:
            task_id:
              type: integer
            status:
              type: string
            status_url:
              type: string
            results_url:
              type: string
            map:
              $ref: '#/components/schemas/MapPayload'

    TaskStatusResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        data:
          type: object
          required: [task_id, status, progress, updated_at]
          properties:
            task_id:
              type: integer
            status:
              type: string
              enum: [queued, validating, ingesting, alteration_mapping, temporal_analysis, forecasting, auditing, publishing, completed, failed]
            progress:
              type: integer
              minimum: 0
              maximum: 100
            updated_at:
              type: string

    TaskProcessingResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        data:
          type: object
          required: [task_id, status]
          properties:
            task_id:
              type: integer
            status:
              type: string

    TaskResultsResponse:
      allOf:
        - $ref: '#/components/schemas/GenericEnvelope'
      properties:
        data:
          type: object
          required: [task, summary, sources, confidence_score, bias_score, scenes, analysis_results, layers, map]
          properties:
            task:
              $ref: '#/components/schemas/TaskDetails'
            summary:
              type: string
              nullable: true
            sources:
              type: array
              items:
                type: string
            confidence_score:
              type: number
              format: float
              nullable: true
            bias_score:
              type: number
              format: float
              nullable: true
            scenes:
              type: array
              items:
                $ref: '#/components/schemas/SceneItem'
            analysis_results:
              type: array
              items:
                $ref: '#/components/schemas/AnalysisResultItem'
            layers:
              type: array
              items:
                $ref: '#/components/schemas/LayerItem'
            map:
              $ref: '#/components/schemas/MapPayload'

    TaskListItem:
      type: object
      properties:
        task_id:
          type: integer
        query:
          type: string
        status:
          type: string
        include_temporal:
          type: integer
          enum: [0, 1]
        include_hyperspectral:
          type: integer
          enum: [0, 1]
        created_at:
          type: string
        updated_at:
          type: string

    TaskDetails:
      type: object
      properties:
        task_id:
          type: integer
        query:
          type: string
        analysis_domains:
          type: array
          items:
            type: string
        sources_config:
          type: array
          items:
            type: string
        area_of_interest:
          $ref: '#/components/schemas/GeoJsonPolygon'
        include_hyperspectral:
          type: integer
          enum: [0, 1]
        include_temporal:
          type: integer
          enum: [0, 1]
        include_forecast:
          type: integer
          enum: [0, 1]
        status:
          type: string
        error_message:
          type: string
          nullable: true
        created_at:
          type: string
        updated_at:
          type: string

    SceneItem:
      type: object
      properties:
        source_id:
          type: integer
        task_id:
          type: integer
        title:
          type: string
        raw_content:
          type: string
        acquired_date:
          type: string
        fetched_at:
          type: string
        scene_id:
          type: string
        cloud_cover:
          type: number
          format: float
          nullable: true
        dataset:
          type: string
        metadata:
          type: object
          additionalProperties: true
        cog_urls:
          type: array
          items:
            type: string

    AnalysisResultItem:
      type: object
      properties:
        id:
          type: integer
        task_id:
          type: integer
        summary:
          type: string
        key_findings:
          type: object
          nullable: true
          additionalProperties: true
        confidence_score:
          type: number
          format: float
        platform:
          type: string
        layer_id:
          type: string
        created_at:
          type: string

    LayerItem:
      type: object
      required: [name, url]
      properties:
        name:
          type: string
        url:
          type: string

    MapPayload:
      type: object
      required: [center, bounds, default_zoom, layers]
      properties:
        center:
          type: object
          required: [lat, lon]
          properties:
            lat:
              type: number
              format: float
            lon:
              type: number
              format: float
        bounds:
          type: object
          required: [min_lon, min_lat, max_lon, max_lat]
          properties:
            min_lon:
              type: number
              format: float
            min_lat:
              type: number
              format: float
            max_lon:
              type: number
              format: float
            max_lat:
              type: number
              format: float
        default_zoom:
          type: integer
          minimum: 0
        layers:
          type: array
          items:
            $ref: '#/components/schemas/LayerItem'

    GeoJsonPolygon:
      type: object
      required: [type, coordinates]
      properties:
        type:
          type: string
          enum: [Polygon]
        coordinates:
          type: array
          minItems: 1
          items:
            type: array
            minItems: 4
            items:
              type: array
              minItems: 2
              maxItems: 2
              items:
                type: number
                format: float
