openapi: 3.0.3
info:
  title: Tankomat Data API
  version: 1.0.0
  description: |
    Fuel price data for Poland and Central Europe. Authenticate with `X-Api-Key`.
    All subscribed plans may call any path; credits consumed per request depend on the `/v1/`, `/v2/`, or `/v3/` prefix.

    **Credit model:** one credit charge per HTTP request. Paginate large datasets with `page` + `limit` (each page = one request).
    `/v1/stations` and `/v2/market/region` require a bounding box — no national single-call dumps.
    `/v3/explain/price` returns a short observation preview; use `/v3/observations` for full history.

    Page caps (server-side): list endpoints 1–25 (default 10); station bbox endpoints 1–50 (default 25).
    Out-of-range `limit` values return HTTP 400. Walk large datasets with `page` (one credit per page).

    **HTTP 402:** trial or monthly credit pool exhausted, or overage spend cap reached (`code` in response body).
    **HTTP 403:** subscription inactive or access policy blocked.
    **HTTP 429:** per-minute rate limit exceeded.

    Download this spec from `GET /data-api/openapi.yaml` or import the Postman collection from `GET /data-api/postman-collection.json`.
  contact:
    email: sales@tankomat.app
servers:
  - url: https://api.tankomat.app
    description: Production
  - url: http://localhost:3000
    description: Local development

security:
  - ApiKeyAuth: []

tags:
  - name: Live
    description: v1 — current prices and stations (1 credit / request)
  - name: Intelligence
    description: v2 — fused prices, market stats, quality (3 credits / request)
  - name: Transparency
    description: v3 — observations, trust, explainability (25 credits / request)

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Secret key from the Tankomat Data dashboard (`tkm_live_…`).

  parameters:
    FuelType:
      name: fuelType
      in: query
      schema:
        type: string
        enum: [unleaded, diesel, superUnleaded, premiumDiesel, lpg]
    Country:
      name: country
      in: query
      schema:
        type: string
        enum: [PL, CZ, DE, SK, AT, GB, IT]
    StationId:
      name: stationId
      in: query
      schema:
        type: string
        format: uuid
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    Limit:
      name: limit
      in: query
      description: |
        Page size for paginated list endpoints. Must be an integer between min and max;
        out-of-range values return HTTP 400. When omitted, the default applies.
      schema:
        type: integer
        minimum: 1
        maximum: 25
        default: 10
    StationsLimit:
      name: limit
      in: query
      description: |
        Max stations returned per bbox page or regional sample query. Must be an integer between min
        and max; out-of-range values return HTTP 400. When omitted, the default applies.
      schema:
        type: integer
        minimum: 1
        maximum: 50
        default: 25
    WindowDays:
      name: windowDays
      in: query
      schema:
        type: integer
        minimum: 1
        default: 7
    BboxMinLat:
      name: minLat
      in: query
      required: true
      description: Bounding box south latitude (required with maxLat, minLng, maxLng on geo endpoints).
      schema:
        type: number
        format: float
    BboxMaxLat:
      name: maxLat
      in: query
      required: true
      schema:
        type: number
        format: float
    BboxMinLng:
      name: minLng
      in: query
      required: true
      schema:
        type: number
        format: float
    BboxMaxLng:
      name: maxLng
      in: query
      required: true
      schema:
        type: number
        format: float

  schemas:
    DisagreementLevel:
      type: string
      enum: [none, low, medium, high]

    AbstractSourceCategory:
      type: string
      enum: [global-aggregator, chain-retail, community]

    AbstractContributingSource:
      type: object
      required: [category, weightShare]
      properties:
        category:
          $ref: '#/components/schemas/AbstractSourceCategory'
        weightShare:
          type: number
          format: float
          minimum: 0
          maximum: 1

    GeoPoint:
      type: object
      required: [lat, lng]
      properties:
        lat:
          type: number
          format: float
        lng:
          type: number
          format: float

    PaginatedMeta:
      type: object
      required: [page, limit, total, totalPages, limitMax, hasNextPage, hasPreviousPage]
      properties:
        page:
          type: integer
          minimum: 1
        limit:
          type: integer
          minimum: 1
          description: Page size for this response.
        limitMax:
          type: integer
          minimum: 1
          description: Maximum allowed limit for this endpoint class.
        total:
          type: integer
          minimum: 0
        totalPages:
          type: integer
          minimum: 1
        hasNextPage:
          type: boolean
        hasPreviousPage:
          type: boolean

    V1CurrentPriceItem:
      type: object
      required: [stationId, fuelType, price, reportedAt]
      properties:
        stationId:
          type: string
          format: uuid
        fuelType:
          type: string
        price:
          type: number
          format: float
        reportedAt:
          type: string
          format: date-time

    V1CurrentPricesResponse:
      allOf:
        - type: object
          required: [apiVersion, generatedAt, items, page, limit, total, totalPages]
          properties:
            apiVersion:
              type: string
              enum: [v1]
            generatedAt:
              type: string
              format: date-time
            items:
              type: array
              items:
                $ref: '#/components/schemas/V1CurrentPriceItem'
        - $ref: '#/components/schemas/PaginatedMeta'

    V1StationItem:
      type: object
      required: [id, slug, name, brand, city, location, prices, lastUpdated]
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        brand:
          type: string
        city:
          type: string
        location:
          $ref: '#/components/schemas/GeoPoint'
        prices:
          type: object
          additionalProperties:
            type: number
            format: float
          description: Map of fuel type name → price (PLN/L)
        lastUpdated:
          type: string
          format: date-time

    V1StationsResponse:
      allOf:
        - type: object
          required: [apiVersion, generatedAt, items, page, limit, total, totalPages]
          properties:
            apiVersion:
              type: string
              enum: [v1]
            generatedAt:
              type: string
              format: date-time
            items:
              type: array
              items:
                $ref: '#/components/schemas/V1StationItem'
        - $ref: '#/components/schemas/PaginatedMeta'

    V2PriceRange:
      type: object
      required: [min, max, spread]
      properties:
        min:
          type: number
          format: float
        max:
          type: number
          format: float
        spread:
          type: number
          format: float

    V2FusedPriceItem:
      type: object
      required: [stationId, fuelType, price, confidence, disagreement, sources]
      properties:
        stationId:
          type: string
          format: uuid
        fuelType:
          type: string
        price:
          type: number
          format: float
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
        disagreement:
          $ref: '#/components/schemas/DisagreementLevel'
        sources:
          type: array
          items:
            $ref: '#/components/schemas/AbstractContributingSource'
        priceRange:
          $ref: '#/components/schemas/V2PriceRange'

    V2PaginatedResponse:
      type: object
      required: [apiVersion, generatedAt, windowDays, items, page, limit, total, totalPages]
      properties:
        apiVersion:
          type: string
          enum: [v2]
        generatedAt:
          type: string
          format: date-time
        windowDays:
          type: integer
          minimum: 1
        items:
          type: array
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer

    V2FusedPricesResponse:
      allOf:
        - $ref: '#/components/schemas/V2PaginatedResponse'
        - type: object
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/V2FusedPriceItem'

    V2CompareV1Slice:
      type: object
      required: [price, reportedAt]
      properties:
        price:
          type: number
          format: float
          nullable: true
        reportedAt:
          type: string
          format: date-time
          nullable: true

    V2CompareV2Slice:
      type: object
      required: [price, confidence, disagreement, sources]
      properties:
        price:
          type: number
          format: float
          nullable: true
        confidence:
          type: number
          format: float
        disagreement:
          $ref: '#/components/schemas/DisagreementLevel'
        sources:
          type: array
          items:
            $ref: '#/components/schemas/AbstractContributingSource'

    V2CompareItem:
      type: object
      required: [stationId, fuelType, v1, v2, delta, deltaPercent, sourceDivergence]
      properties:
        stationId:
          type: string
          format: uuid
        fuelType:
          type: string
        v1:
          $ref: '#/components/schemas/V2CompareV1Slice'
        v2:
          $ref: '#/components/schemas/V2CompareV2Slice'
        delta:
          type: number
          format: float
          nullable: true
        deltaPercent:
          type: number
          format: float
          nullable: true
        sourceDivergence:
          type: boolean

    V2ComparePricesResponse:
      allOf:
        - $ref: '#/components/schemas/V2PaginatedResponse'
        - type: object
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/V2CompareItem'

    V2MarketRegionFused:
      type: object
      required: [median, min, max, meanConfidence]
      properties:
        median:
          type: number
          format: float
          nullable: true
        min:
          type: number
          format: float
          nullable: true
        max:
          type: number
          format: float
          nullable: true
        meanConfidence:
          type: number
          format: float

    V2MarketRegionResponse:
      type: object
      required: [apiVersion, generatedAt, fuelType, windowDays, stationCount, fused, disagreement]
      properties:
        apiVersion:
          type: string
          enum: [v2]
        generatedAt:
          type: string
          format: date-time
        fuelType:
          type: string
        windowDays:
          type: integer
        stationCount:
          type: integer
        fused:
          $ref: '#/components/schemas/V2MarketRegionFused'
        disagreement:
          type: object
          additionalProperties:
            type: integer
          description: Count of stations per disagreement level

    V2StationQualityItem:
      type: object
      required:
        - stationId
        - stationBrand
        - fuelType
        - sourceCount
        - confidence
        - disagreement
        - freshnessHours
        - qualityScore
      properties:
        stationId:
          type: string
          format: uuid
        stationBrand:
          type: string
        fuelType:
          type: string
        sourceCount:
          type: integer
        confidence:
          type: number
          format: float
        disagreement:
          $ref: '#/components/schemas/DisagreementLevel'
        freshnessHours:
          type: number
          format: float
          nullable: true
        qualityScore:
          type: number
          format: float

    V2StationQualityResponse:
      allOf:
        - $ref: '#/components/schemas/V2PaginatedResponse'
        - type: object
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/V2StationQualityItem'

    V3ObservationItem:
      type: object
      required: [stationId, fuelType, sourceId, price, score, reportedAt, extractionConfidence]
      properties:
        stationId:
          type: string
          format: uuid
        fuelType:
          type: string
        sourceId:
          type: string
        price:
          type: number
          format: float
        score:
          type: number
          format: float
        reportedAt:
          type: string
          format: date-time
        extractionConfidence:
          type: number
          format: float
          nullable: true

    V3PaginatedResponse:
      type: object
      required: [apiVersion, generatedAt, windowDays, items, page, limit, total, totalPages]
      properties:
        apiVersion:
          type: string
          enum: [v3]
        generatedAt:
          type: string
          format: date-time
        windowDays:
          type: integer
        items:
          type: array
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer

    V3ObservationsResponse:
      allOf:
        - $ref: '#/components/schemas/V3PaginatedResponse'
        - type: object
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/V3ObservationItem'

    V3DisagreementDetail:
      type: object
      required: [level, spread, minPrice, maxPrice, count]
      properties:
        level:
          $ref: '#/components/schemas/DisagreementLevel'
        spread:
          type: number
          format: float
        minPrice:
          type: number
          format: float
        maxPrice:
          type: number
          format: float
        count:
          type: integer

    V3ContributingSource:
      type: object
      required: [sourceId, price, weight, score, inDomain]
      properties:
        sourceId:
          type: string
        price:
          type: number
          format: float
        weight:
          type: number
          format: float
        score:
          type: number
          format: float
        inDomain:
          type: boolean
        reportedAt:
          type: string
          format: date-time

    V3Candidate:
      type: object
      required: [rank, sourceId, price, score]
      properties:
        rank:
          type: integer
        sourceId:
          type: string
        price:
          type: number
          format: float
        score:
          type: number
          format: float
        reportedAt:
          type: string
          format: date-time

    V3ExtendedFusedItem:
      type: object
      required: [stationId, fuelType, estimate, confidence, disagreement, contributingSources, candidates]
      properties:
        stationId:
          type: string
          format: uuid
        fuelType:
          type: string
        estimate:
          type: number
          format: float
          nullable: true
        confidence:
          type: number
          format: float
        disagreement:
          $ref: '#/components/schemas/V3DisagreementDetail'
        contributingSources:
          type: array
          items:
            $ref: '#/components/schemas/V3ContributingSource'
        candidates:
          type: array
          items:
            $ref: '#/components/schemas/V3Candidate'

    V3ExtendedFusedResponse:
      type: object
      required: [apiVersion, generatedAt, item]
      properties:
        apiVersion:
          type: string
          enum: [v3]
        generatedAt:
          type: string
          format: date-time
        item:
          $ref: '#/components/schemas/V3ExtendedFusedItem'
          nullable: true

    V3TrustSourceContext:
      type: object
      properties:
        fuelType:
          type: string
        region:
          type: string
        stationType:
          type: string
        precisionMultiplier:
          type: number
          format: float
        sampleCount:
          type: integer
        meanAbsRelativeError:
          type: number
          format: float
        bucketKey:
          type: string
        updatedAt:
          type: string
          format: date-time

    V3TrustSourceItem:
      type: object
      required: [sourceId, precision, coverage, stability, scope]
      properties:
        sourceId:
          type: string
        precision:
          type: number
          format: float
        coverage:
          type: number
          format: float
        stability:
          type: number
          format: float
        scope:
          type: string
          enum: [global, chain]
        adjustedPrecision:
          type: number
          format: float
        context:
          $ref: '#/components/schemas/V3TrustSourceContext'

    V3TrustSourcesResponse:
      allOf:
        - type: object
          required: [apiVersion, generatedAt, items, page, limit, total, totalPages]
          properties:
            apiVersion:
              type: string
              enum: [v3]
            generatedAt:
              type: string
              format: date-time
            items:
              type: array
              items:
                $ref: '#/components/schemas/V3TrustSourceItem'
        - $ref: '#/components/schemas/PaginatedMeta'

    V3ExplainV1Slice:
      type: object
      required: [price, source, reportedAt]
      properties:
        price:
          type: number
          format: float
          nullable: true
        source:
          type: string
          nullable: true
        reportedAt:
          type: string
          format: date-time
          nullable: true

    V3ExplainVerdict:
      type: object
      required: [domain, confidence, likelySource, reasons]
      properties:
        domain:
          type: string
        confidence:
          type: number
          format: float
        likelySource:
          type: string
          nullable: true
        reasons:
          type: array
          items:
            type: string

    V3ExplainPriceResponse:
      type: object
      required:
        - apiVersion
        - generatedAt
        - stationId
        - fuelType
        - v1
        - v2
        - observations
        - observationsTotal
        - observationsTruncated
        - verdict
      properties:
        apiVersion:
          type: string
          enum: [v3]
        generatedAt:
          type: string
          format: date-time
        stationId:
          type: string
          format: uuid
        fuelType:
          type: string
        v1:
          $ref: '#/components/schemas/V3ExplainV1Slice'
        v2:
          $ref: '#/components/schemas/V3ExtendedFusedItem'
        observations:
          type: array
          description: Preview slice only — paginate GET /v3/observations for full history.
          items:
            $ref: '#/components/schemas/V3ObservationItem'
        observationsTotal:
          type: integer
          minimum: 0
          description: Total observations in the fusion window (may exceed observations.length).
        observationsTruncated:
          type: boolean
          description: True when observationsTotal exceeds the preview length.
        verdict:
          $ref: '#/components/schemas/V3ExplainVerdict'

    ErrorResponse:
      type: object
      properties:
        statusCode:
          type: integer
        message:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        error:
          type: string
        code:
          type: string
          description: Machine-readable billing or quota code when status is 402

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Subscription inactive or tier not allowed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PaymentRequired:
      description: Trial or monthly credit pool exhausted, or overage spend cap reached
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: Invalid or missing required query parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

paths:
  /v1/prices/current:
    get:
      tags: [Live]
      summary: Latest price per station
      operationId: v1GetCurrentPrices
      parameters:
        - $ref: '#/components/parameters/StationId'
        - $ref: '#/components/parameters/FuelType'
        - $ref: '#/components/parameters/Country'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Paginated current prices
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1CurrentPricesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v1/stations:
    get:
      tags: [Live]
      summary: Stations in a bounding box (paginated)
      description: |
        Requires minLat, maxLat, minLng, and maxLng. Walk large areas with `page` — each page consumes one credit.
      operationId: v1GetStations
      parameters:
        - $ref: '#/components/parameters/FuelType'
        - $ref: '#/components/parameters/Country'
        - $ref: '#/components/parameters/BboxMinLat'
        - $ref: '#/components/parameters/BboxMaxLat'
        - $ref: '#/components/parameters/BboxMinLng'
        - $ref: '#/components/parameters/BboxMaxLng'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/StationsLimit'
      responses:
        '200':
          description: Stations with v1 prices
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1StationsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v2/prices/fused:
    get:
      tags: [Intelligence]
      summary: Fused price with confidence
      operationId: v2GetFusedPrices
      parameters:
        - $ref: '#/components/parameters/StationId'
        - $ref: '#/components/parameters/FuelType'
        - $ref: '#/components/parameters/Country'
        - $ref: '#/components/parameters/WindowDays'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Fused prices
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2FusedPricesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v2/prices/compare:
    get:
      tags: [Intelligence]
      summary: Side-by-side price views
      operationId: v2ComparePrices
      parameters:
        - $ref: '#/components/parameters/StationId'
        - $ref: '#/components/parameters/FuelType'
        - $ref: '#/components/parameters/Country'
        - $ref: '#/components/parameters/WindowDays'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Comparison payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2ComparePricesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v2/market/region:
    get:
      tags: [Intelligence]
      summary: Regional price statistics in a bounding box
      description: Requires minLat, maxLat, minLng, and maxLng — no country-wide single-call aggregates.
      operationId: v2MarketRegion
      parameters:
        - name: fuelType
          in: query
          required: true
          schema:
            type: string
            enum: [unleaded, diesel, superUnleaded, premiumDiesel, lpg]
        - $ref: '#/components/parameters/WindowDays'
        - $ref: '#/components/parameters/BboxMinLat'
        - $ref: '#/components/parameters/BboxMaxLat'
        - $ref: '#/components/parameters/BboxMinLng'
        - $ref: '#/components/parameters/BboxMaxLng'
        - $ref: '#/components/parameters/StationsLimit'
      responses:
        '200':
          description: Regional aggregates
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2MarketRegionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v2/meta/station-quality:
    get:
      tags: [Intelligence]
      summary: Station quality score
      operationId: v2StationQuality
      parameters:
        - $ref: '#/components/parameters/StationId'
        - $ref: '#/components/parameters/FuelType'
        - $ref: '#/components/parameters/WindowDays'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Quality metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V2StationQualityResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v3/observations:
    get:
      tags: [Transparency]
      summary: Observations by source
      operationId: v3Observations
      parameters:
        - $ref: '#/components/parameters/StationId'
        - $ref: '#/components/parameters/FuelType'
        - name: sourceId
          in: query
          schema:
            type: string
        - $ref: '#/components/parameters/WindowDays'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Raw observations
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V3ObservationsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v3/prices/fused:
    get:
      tags: [Transparency]
      summary: Extended fused price with sources
      operationId: v3ExtendedFused
      parameters:
        - name: stationId
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: fuelType
          in: query
          required: true
          schema:
            type: string
            enum: [unleaded, diesel, superUnleaded, premiumDiesel, lpg]
        - $ref: '#/components/parameters/WindowDays'
      responses:
        '200':
          description: Extended fusion output
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V3ExtendedFusedResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v3/trust/sources:
    get:
      tags: [Transparency]
      summary: Source trust profile
      operationId: v3TrustSources
      parameters:
        - $ref: '#/components/parameters/FuelType'
        - name: region
          in: query
          schema:
            type: string
        - name: stationType
          in: query
          schema:
            type: string
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Trust scores
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V3TrustSourcesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /v3/explain/price:
    get:
      tags: [Transparency]
      summary: Why this price won (observation preview)
      description: |
        Returns a short observation preview (see `observationsTotal` / `observationsTruncated`).
        Paginate GET /v3/observations for the full list — each page consumes credits separately.
      operationId: v3ExplainPrice
      parameters:
        - name: stationId
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: fuelType
          in: query
          required: true
          schema:
            type: string
            enum: [unleaded, diesel, superUnleaded, premiumDiesel, lpg]
        - $ref: '#/components/parameters/WindowDays'
      responses:
        '200':
          description: Explainability payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V3ExplainPriceResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
