openapi: 3.1.0
info:
  title: PriceFeed API
  version: "1.0.0"
  description: >
    Reliable crypto, fiat and precious-metal exchange rates — aggregated from
    multiple sources with a weighted median, outlier rejection and per-source
    transparency. Real-time and historical, served from one clean REST API.
  contact:
    name: PriceFeed
    url: https://pricefeed.online
  license:
    name: Terms of Use
    url: https://pricefeed.online/terms
servers:
  - url: https://pricefeed.online
    description: Production
tags:
  - name: Rates
    description: Latest, converted and historical rates
  - name: Data quality
    description: Per-source breakdown and service health
  - name: Catalog
    description: Supported assets and pairs
  - name: CryptoCompare-compatible
    description: Drop-in replacement for the CryptoCompare /data API
security:
  - {}                      # public access (rate-limited per IP)
  - ApiKeyAuth: []          # higher limits with a key
paths:
  /api/v1/rates/latest:
    get:
      tags: [Rates]
      summary: Latest aggregated rates
      description: >
        Without parameters returns every active pair. With `base` (+ optional
        `quotes`) returns that base against the requested quotes. With `source`
        returns the price from a single exchange instead of the median.
      parameters:
        - { name: base, in: query, required: false, schema: { type: string }, example: BTC }
        - name: quotes
          in: query
          required: false
          schema: { type: string }
          description: Comma-separated quote currencies. Default USD.
          example: USD,EUR,UAH
        - name: source
          in: query
          required: false
          schema: { type: string }
          description: Return a single source's price (direct pairs only), e.g. `binance`.
          example: binance
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/LatestByBase'
                  - $ref: '#/components/schemas/LatestAllPairs'
                  - $ref: '#/components/schemas/LatestBySource'
        "404": { $ref: '#/components/responses/Error' }
        "429": { $ref: '#/components/responses/Error' }
  /api/v1/rates/convert:
    get:
      tags: [Rates]
      summary: Convert an amount between two assets
      parameters:
        - { name: from, in: query, required: true, schema: { type: string }, example: BTC }
        - { name: to, in: query, required: true, schema: { type: string }, example: UAH }
        - { name: amount, in: query, required: false, schema: { type: number, default: 1 }, example: 0.05 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Convert' }
        "400": { $ref: '#/components/responses/Error' }
        "404": { $ref: '#/components/responses/Error' }
  /api/v1/rates/history:
    get:
      tags: [Rates]
      summary: Historical OHLC candles
      parameters:
        - { name: base, in: query, required: true, schema: { type: string }, example: BTC }
        - { name: quote, in: query, required: false, schema: { type: string, default: USD }, example: USD }
        - name: period
          in: query
          required: false
          schema: { type: string, enum: [1d, 7d, 30d, 90d], default: 7d }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/History' }
        "400": { $ref: '#/components/responses/Error' }
        "404": { $ref: '#/components/responses/Error' }
  /api/v1/rates/breakdown:
    get:
      tags: [Data quality]
      summary: Per-source price breakdown (direct pairs)
      description: >
        Shows each source's price for a direct pair, its deviation from the
        served weighted median, its weight, and whether it was rejected as an
        outlier. Not available for derived pairs (e.g. BTC/UAH via USDT).
      parameters:
        - { name: base, in: query, required: true, schema: { type: string }, example: BCH }
        - { name: quote, in: query, required: true, schema: { type: string }, example: USDT }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Breakdown' }
        "404": { $ref: '#/components/responses/Error' }
  /api/v1/rates/sources:
    get:
      tags: [Data quality]
      summary: List data sources and their health
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Sources' } } } }
  /api/v1/rates/status:
    get:
      tags: [Data quality]
      summary: Service and data health
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Status' } } } }
  /api/v1/assets:
    get:
      tags: [Catalog]
      summary: Supported assets
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Assets' } } } }
  /api/v1/pairs:
    get:
      tags: [Catalog]
      summary: Supported pairs
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Pairs' } } } }
  /data/price:
    get:
      tags: [CryptoCompare-compatible]
      summary: Single-symbol multi-quote price
      parameters:
        - { name: fsym, in: query, required: true, schema: { type: string }, example: BTC }
        - { name: tsyms, in: query, required: true, schema: { type: string }, example: USD,EUR,UAH }
      responses:
        "200":
          description: '{"USD": 104250.12, ...}'
          content:
            application/json:
              schema: { type: object, additionalProperties: { type: number } }
  /data/pricemulti:
    get:
      tags: [CryptoCompare-compatible]
      summary: Multi-symbol multi-quote price
      parameters:
        - { name: fsyms, in: query, required: true, schema: { type: string }, example: BTC,ETH }
        - { name: tsyms, in: query, required: true, schema: { type: string }, example: USD,UAH }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: object, additionalProperties: { type: object, additionalProperties: { type: number } } }
  /data/pricemultifull:
    get:
      tags: [CryptoCompare-compatible]
      summary: Full multi-symbol data (RAW + DISPLAY)
      parameters:
        - { name: fsyms, in: query, required: true, schema: { type: string }, example: BTC }
        - { name: tsyms, in: query, required: true, schema: { type: string }, example: USD }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object } } } }
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: "Optional. Without a key: 60 req/min per IP. With a key: 120 req/min."
  responses:
    Error:
      description: Error response
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
  schemas:
    Error:
      type: object
      properties:
        success: { type: boolean, example: false }
        error:
          type: object
          properties:
            code: { type: string, example: PAIR_NOT_FOUND }
            message: { type: string, example: "Requested currency pair [BTC/XYZ] is not available." }
    LatestByBase:
      type: object
      properties:
        success: { type: boolean }
        base: { type: string, example: BTC }
        rates:
          type: object
          additionalProperties: { type: number }
          example: { USD: 104250.12, EUR: 96480.55, UAH: 4329815.40 }
        meta:
          type: object
          properties:
            source: { type: string, example: aggregated }
            source_count: { type: integer, example: 4 }
            stale: { type: boolean }
            updated_at: { type: string, format: date-time }
    LatestAllPairs:
      type: object
      properties:
        success: { type: boolean }
        rates:
          type: array
          items:
            type: object
            properties:
              pair: { type: string, example: BTC/USD }
              base: { type: string }
              quote: { type: string }
              price: { type: number }
              spread_percent: { type: number, nullable: true }
              change_24h: { type: number, nullable: true }
              source_count: { type: integer }
              stale: { type: boolean }
              updated_at: { type: string, format: date-time }
    LatestBySource:
      type: object
      properties:
        success: { type: boolean }
        base: { type: string }
        quote: { type: string }
        source: { type: string, example: binance }
        price: { type: number }
        bid: { type: number, nullable: true }
        ask: { type: number, nullable: true }
        change_24h: { type: number, nullable: true }
        volume_24h: { type: number, nullable: true }
        deviation_percent: { type: number, nullable: true }
        is_outlier: { type: boolean }
        age_seconds: { type: integer, nullable: true }
        updated_at: { type: string, format: date-time, nullable: true }
    Convert:
      type: object
      properties:
        success: { type: boolean }
        from: { type: string }
        to: { type: string }
        amount: { type: number }
        rate: { type: number }
        result: { type: number }
        meta:
          type: object
          properties:
            stale: { type: boolean }
            via: { type: string, nullable: true, description: "Bridge asset for derived pairs, e.g. USDT" }
            updated_at: { type: string, format: date-time }
    History:
      type: object
      properties:
        success: { type: boolean }
        base: { type: string }
        quote: { type: string }
        period: { type: string, example: 7d }
        interval: { type: string, example: 1h }
        points:
          type: array
          items:
            type: object
            properties:
              timestamp: { type: string, format: date-time }
              open: { type: number }
              high: { type: number }
              low: { type: number }
              close: { type: number }
    Breakdown:
      type: object
      properties:
        success: { type: boolean }
        base: { type: string }
        quote: { type: string }
        median: { type: number, description: "Served price (weighted median)" }
        plain_median: { type: number, description: "Unweighted median, for reference" }
        spread_percent: { type: number, nullable: true }
        source_count: { type: integer }
        outliers_removed: { type: integer }
        sources:
          type: array
          items:
            type: object
            properties:
              source: { type: string, example: binance }
              name: { type: string, nullable: true }
              type: { type: string, nullable: true }
              status: { type: string, nullable: true }
              price: { type: number }
              bid: { type: number, nullable: true }
              ask: { type: number, nullable: true }
              change_24h: { type: number, nullable: true }
              volume_24h: { type: number, nullable: true }
              age_seconds: { type: integer, nullable: true }
              fetched_at: { type: string, format: date-time, nullable: true }
              deviation_percent: { type: number, nullable: true }
              weight: { type: number }
              is_outlier: { type: boolean }
    Sources:
      type: object
      properties:
        success: { type: boolean }
        sources:
          type: array
          items:
            type: object
            properties:
              slug: { type: string }
              name: { type: string }
              type: { type: string }
              status: { type: string }
              last_success_at: { type: string, format: date-time, nullable: true }
    Status:
      type: object
      properties:
        success: { type: boolean }
        status: { type: string, enum: [operational, degraded, partial_outage, outage] }
        sources:
          type: object
          additionalProperties: { type: string }
        cache:
          type: object
          properties:
            status: { type: string }
            driver: { type: string }
        metrics:
          type: object
          properties:
            window_days: { type: integer }
            latency_p50_ms: { type: integer, nullable: true }
            latency_p99_ms: { type: integer, nullable: true }
            uptime_pct: { type: number, nullable: true }
            sources_online: { type: integer }
            sources_total: { type: integer }
            data_fresh_pct: { type: number, nullable: true }
            cache_status: { type: string }
        updated_at: { type: string, format: date-time }
    Assets:
      type: object
      properties:
        success: { type: boolean }
        assets:
          type: array
          items:
            type: object
            properties:
              symbol: { type: string }
              name: { type: string }
              type: { type: string, enum: [crypto, fiat, metal] }
              is_stablecoin: { type: boolean }
              precision: { type: integer }
    Pairs:
      type: object
      properties:
        success: { type: boolean }
        pairs:
          type: array
          items:
            type: object
            properties:
              pair: { type: string }
              base: { type: string }
              quote: { type: string }
              update_interval_seconds: { type: integer }
