openapi: 3.1.0
info:
  title: KAI GPU Card-Hour Pricing API
  version: 2.0.0-alpha.1
  license:
    name: Proprietary
    url: /legal.html
  description: |
    Read GPU rental products and card-hour prices in 31 snapshot-consistent
    currencies. Products are
    grouped by GPU model and VRAM, with region as the main product distinction.
    SXM is an optional value-added attribute; ordinary products have a null
    formFactor. Published products are available for every rented hour.
servers:
  - url: https://pricing.kai.com
    description: Production
  - url: /
    description: Same-origin deployment
tags:
  - name: Service
    description: Service discovery, health and public metadata.
  - name: Indexes
    description: Aggregate GPU card-hour price indexes.
  - name: Catalog
    description: Stable GPU model and regional product catalog.
  - name: Prices
    description: Current snapshots, materialized history and update streams.
security: []
paths:
  /v1:
    get:
      operationId: listEndpoints
      tags: [Service]
      summary: List available data endpoints
      responses:
        "200":
          description: Endpoint index
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /healthz:
    get:
      operationId: getHealth
      tags: [Service]
      summary: Check service health
      responses:
        "200":
          description: Service is available
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /readyz:
    get:
      operationId: getReadiness
      tags: [Service]
      summary: Check whether prices are ready
      responses:
        "200":
          description: Prices are ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "503":
          description: Prices are not ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceStatus"
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /v1/meta:
    get:
      operationId: getMetadata
      tags: [Service]
      summary: List available price dimensions
      responses:
        "200":
          description: Supported GPU models, regions and form factors
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MetadataEnvelope"
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /v1/indexes/kai50:
    get:
      operationId: getKai50
      tags: [Indexes]
      summary: Get the KAI 50 compute price index and daily OHLC candles
      parameters:
        - name: days
          in: query
          description: Number of recent UTC daily index points to return.
          schema:
            type: integer
            minimum: 30
            maximum: 1461
            default: 1461
      responses:
        "200":
          description: Current KAI 50 level, changes, daily OHLC candles and constituents
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Kai50Envelope"
        "400":
          $ref: "#/components/responses/BadRequest"
  /v1/indexes/720:
    get:
      operationId: list720HourIndexes
      tags: [Indexes]
      summary: Get all five future 720-hour GPU rental indexes
      responses:
        "200":
          description: Five snapshot-backed hourly outlooks and 720-hour cumulative costs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ForwardIndexListEnvelope"
        "503":
          description: No validated 720-hour index release is active
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /v1/indexes/{slug}:
    get:
      operationId: get720HourIndex
      tags: [Indexes]
      summary: Get one future 720-hour GPU rental index
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            enum: [720-gb300, 720-b300, 720-b200, 720-h200, 720-h100]
      responses:
        "200":
          description: One validated hourly outlook with exactly 720 price points
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ForwardIndexEnvelope"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          description: No validated 720-hour index release is active
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /v1/indexes/{slug}/candles:
    get:
      operationId: get720HourIndexCandles
      tags: [Indexes]
      summary: Get historical OHLC candles for one 720-hour cumulative-cost index
      description: |
        The time axis is historical. Each candle records how the named 720-hour
        index changed at past observation times; it is not the 720 future hourly
        price-point breakdown returned by /v1/indexes/{slug}.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
            enum: [720-gb300, 720-b300, 720-b200, 720-h200, 720-h100]
        - name: interval
          in: query
          schema:
            type: string
            enum: [1h, 4h, 1d, 1w]
            default: 1d
        - name: days
          in: query
          schema:
            type: integer
            minimum: 7
            maximum: 1461
            default: 1096
      responses:
        "200":
          description: Historical 720-index OHLC candles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ForwardIndexCandleEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          description: Materialized 720-index history is not ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /v1/catalog:
    get:
      operationId: listCatalog
      tags: [Catalog]
      summary: List products grouped by GPU model and VRAM
      parameters:
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/GpuModel"
        - $ref: "#/components/parameters/Region"
        - $ref: "#/components/parameters/FormFactor"
      responses:
        "200":
          description: Grouped product catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
  /v1/products:
    get:
      operationId: listProducts
      tags: [Catalog]
      summary: List price products
      parameters:
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/GpuModel"
        - $ref: "#/components/parameters/Region"
        - $ref: "#/components/parameters/FormFactor"
      responses:
        "200":
          description: Products without time-varying prices
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
  /v1/products/{productId}:
    get:
      operationId: getProduct
      tags: [Catalog]
      summary: Get one product
      parameters:
        - $ref: "#/components/parameters/ProductIdPath"
      responses:
        "200":
          description: Product
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SingleProductEnvelope"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/prices:
    get:
      operationId: listPrices
      tags: [Prices]
      summary: List current GPU card-hour prices
      parameters:
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/GpuModel"
        - $ref: "#/components/parameters/Region"
        - $ref: "#/components/parameters/FormFactor"
        - $ref: "#/components/parameters/At"
      responses:
        "200":
          description: Current prices with a 31-currency priceCurrencies map
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
  /v1/prices/export.csv:
    get:
      operationId: exportPricesCsv
      tags: [Prices]
      summary: Download current prices as CSV
      parameters:
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/GpuModel"
        - $ref: "#/components/parameters/Region"
        - $ref: "#/components/parameters/FormFactor"
        - $ref: "#/components/parameters/At"
      responses:
        "200":
          description: UTF-8 CSV containing product dimensions and prices
          headers:
            Content-Disposition:
              schema:
                type: string
          content:
            text/csv:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
  /v1/prices/{productId}:
    get:
      operationId: getPrice
      tags: [Prices]
      summary: Get one price
      parameters:
        - $ref: "#/components/parameters/ProductIdPath"
        - $ref: "#/components/parameters/At"
      responses:
        "200":
          description: Price
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SingleQuoteEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/prices/{productId}/history:
    get:
      operationId: getPriceHistory
      tags: [Prices]
      summary: Get price history for a custom interval
      parameters:
        - $ref: "#/components/parameters/ProductIdPath"
        - name: from
          in: query
          schema:
            oneOf:
              - type: integer
              - type: string
                format: date-time
        - name: to
          in: query
          schema:
            oneOf:
              - type: integer
              - type: string
                format: date-time
        - name: intervalMs
          in: query
          schema:
            type: integer
            minimum: 3600000
            default: 3600000
      responses:
        "200":
          description: Price history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HistoryEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/prices/{productId}/history/hourly:
    get:
      operationId: getHourlyPriceHistory
      tags: [Prices]
      summary: Get up to 36 months of materialized hourly prices with signed pagination
      parameters:
        - $ref: "#/components/parameters/ProductIdPath"
        - name: from
          in: query
          schema:
            oneOf:
              - type: integer
              - type: string
                format: date-time
        - name: months
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 36
            default: 3
        - name: to
          in: query
          schema:
            oneOf:
              - type: integer
              - type: string
                format: date-time
        - name: format
          in: query
          schema:
            type: string
            enum: [json, csv]
            default: json
        - name: cursor
          in: query
          description: Opaque signed cursor returned by the previous page.
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 10000
            default: 2500
      responses:
        "200":
          description: Hourly price history in the requested format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HourlyHistoryEnvelope"
            text/csv:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/prices/{productId}/candles:
    get:
      operationId: getPriceCandles
      tags: [Prices]
      summary: Get up to 36 months of minute-derived OHLC card-hour price candles
      parameters:
        - $ref: "#/components/parameters/ProductIdPath"
        - name: from
          in: query
          schema:
            oneOf:
              - type: integer
              - type: string
                format: date-time
        - name: months
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 36
            default: 12
        - name: interval
          in: query
          schema:
            type: string
            enum: [1h, 4h, 1d, 1w]
            default: 1d
        - name: to
          in: query
          schema:
            oneOf:
              - type: integer
              - type: string
                format: date-time
      responses:
        "200":
          description: OHLC candles aggregated from the release's minute-resolution source
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CandleEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
  /v1/stream/prices:
    get:
      operationId: streamPrices
      tags: [Prices]
      summary: Subscribe to current price updates
      parameters:
        - $ref: "#/components/parameters/ProductId"
        - $ref: "#/components/parameters/GpuModel"
        - $ref: "#/components/parameters/Region"
        - $ref: "#/components/parameters/FormFactor"
        - name: intervalMs
          in: query
          schema:
            type: integer
            minimum: 1000
            maximum: 60000
            default: 2000
      responses:
        "200":
          description: Price update stream
          content:
            text/event-stream:
              schema:
                type: string
        "503":
          description: Update stream is temporarily full
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
components:
  parameters:
    ProductIdPath:
      name: productId
      in: path
      required: true
      schema:
        type: string
    ProductId:
      name: productId
      in: query
      description: Repeat the parameter or provide comma-separated values.
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
    GpuModel:
      name: gpuModel
      in: query
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
    Region:
      name: region
      in: query
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
    FormFactor:
      name: formFactor
      in: query
      schema:
        type: array
        items:
          type: string
          enum: [SXM]
      style: form
      explode: true
    At:
      name: at
      in: query
      description: ISO-8601 timestamp or epoch milliseconds; defaults to now.
      schema:
        oneOf:
          - type: integer
          - type: string
            format: date-time
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    NotFound:
      description: Unknown productId
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    TooManyRequests:
      description: Public request limit exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
  schemas:
    ServiceStatus:
      type: object
      required: [status]
      properties:
        status:
          type: string
    Availability:
      type: object
      required: [status, guaranteedForRentedHours, policy]
      properties:
        status:
          type: string
          const: available
        guaranteedForRentedHours:
          type: boolean
          const: true
        policy:
          type: string
          const: ALL_RENTED_HOURS
    Product:
      type: object
      required:
        - id
        - gpuName
        - vram
        - gpuModel
        - region
        - availableFrom
        - availability
        - cpuCoresOptions
        - systemMemoryGbOptions
        - gpuCountOptions
        - billingModes
        - currency
        - unit
      properties:
        id:
          type: string
        gpuName:
          type: string
        vram:
          type: [string, "null"]
        gpuModel:
          type: string
        formFactor:
          type: string
          description: Optional SXM value-added attribute; omitted for an ordinary product
          const: SXM
        region:
          type: string
        availableFrom:
          type: string
          format: date-time
          description: Earliest market availability; no price is generated before this time
        availability:
          $ref: "#/components/schemas/Availability"
        cpuCoresOptions:
          type: array
          items:
            type: number
        systemMemoryGbOptions:
          type: array
          items:
            type: number
        gpuCountOptions:
          type: array
          items:
            type: number
        billingModes:
          type: array
          items:
            type: string
        currency:
          type: string
          const: CNY
        unit:
          type: string
          const: GPU_HOUR
    Quote:
      allOf:
        - $ref: "#/components/schemas/Product"
        - type: object
          required:
            - productId
            - price
            - priceCny
            - priceUsd
            - priceInr
            - priceTwd
            - priceMyr
            - priceSgd
            - priceCurrencies
            - asOf
            - asOfEpochMs
            - dataVersion
            - priceStatus
          properties:
            productId:
              type: string
            price:
              type: string
            priceCny:
              type: string
            priceUsd:
              type: string
            priceInr:
              type: string
            priceTwd:
              type: string
            priceMyr:
              type: string
            priceSgd:
              type: string
            priceCurrencies:
              type: object
              description: Price per GPU-hour keyed by ISO 4217 currency code
              required: [CNY, USD, EUR, JPY, KRW, GBP, HKD, SGD, TWD, INR, MYR, IDR, PHP, AUD, CAD, THB, NZD, CHF, SEK, NOK, DKK, PLN, CZK, HUF, RON, TRY, BRL, MXN, ZAR, ILS, ISK]
              minProperties: 31
              properties:
                CNY: {type: string}
                USD: {type: string}
                EUR: {type: string}
                JPY: {type: string}
                KRW: {type: string}
                GBP: {type: string}
                HKD: {type: string}
                SGD: {type: string}
                TWD: {type: string}
                INR: {type: string}
                MYR: {type: string}
                IDR: {type: string}
                PHP: {type: string}
                AUD: {type: string}
                CAD: {type: string}
                THB: {type: string}
                NZD: {type: string}
                CHF: {type: string}
                SEK: {type: string}
                NOK: {type: string}
                DKK: {type: string}
                PLN: {type: string}
                CZK: {type: string}
                HUF: {type: string}
                RON: {type: string}
                TRY: {type: string}
                BRL: {type: string}
                MXN: {type: string}
                ZAR: {type: string}
                ILS: {type: string}
                ISK: {type: string}
            asOf:
              type: string
              format: date-time
            asOfEpochMs:
              type: integer
            dataVersion:
              type: integer
            priceStatus:
              type: string
              enum: [current, delayed]
    HistoryPoint:
      type: object
      required: [asOf, asOfEpochMs, price, priceCny, priceUsd]
      properties:
        asOf:
          type: string
          format: date-time
        asOfEpochMs:
          type: integer
        price:
          type: string
        priceCny:
          type: string
        priceUsd:
          type: string
    CatalogGroup:
      type: object
      required:
        - id
        - gpuName
        - vram
        - displayName
        - productCount
        - regionCount
        - regions
        - formFactors
        - availability
        - products
      properties:
        id:
          type: string
        gpuName:
          type: string
        vram:
          type: [string, "null"]
        displayName:
          type: string
        productCount:
          type: integer
        regionCount:
          type: integer
        regions:
          type: array
          items:
            type: string
        formFactors:
          type: array
          items:
            type: string
            const: SXM
        availability:
          type: object
          additionalProperties: true
        products:
          type: array
          items:
            $ref: "#/components/schemas/Product"
    ApiMeta:
      type: object
      required: [asOf, effectiveAt, validUntil, snapshotId, dataVersion, count, currencies, currencyRates]
      properties:
        asOf:
          type: string
          format: date-time
          description: Published price batch timestamp for live price responses
        effectiveAt:
          type: string
          format: date-time
        snapshotId:
          type: string
        validUntil:
          type: string
          format: date-time
          description: End of the current shared live price batch
        refreshIntervalMs:
          type: integer
          description: Server-side live price publication interval
          example: 300000
        dataVersion:
          type: integer
        count:
          type: integer
        currencies:
          type: array
          items:
            type: string
            enum: [CNY, USD, EUR, JPY, KRW, GBP, HKD, SGD, TWD, INR, MYR, IDR, PHP, AUD, CAD, THB, NZD, CHF, SEK, NOK, DKK, PLN, CZK, HUF, RON, TRY, BRL, MXN, ZAR, ILS, ISK]
          minItems: 31
          uniqueItems: true
        currencyRates:
          type: object
          description: USD-to-quote rates fixed for this immutable snapshot
          required: [CNY, USD, EUR, JPY, KRW, GBP, HKD, SGD, TWD, INR, MYR, IDR, PHP, AUD, CAD, THB, NZD, CHF, SEK, NOK, DKK, PLN, CZK, HUF, RON, TRY, BRL, MXN, ZAR, ILS, ISK]
          minProperties: 31
          properties:
            CNY: {type: string}
            USD: {type: string}
            EUR: {type: string}
            JPY: {type: string}
            KRW: {type: string}
            GBP: {type: string}
            HKD: {type: string}
            SGD: {type: string}
            TWD: {type: string}
            INR: {type: string}
            MYR: {type: string}
            IDR: {type: string}
            PHP: {type: string}
            AUD: {type: string}
            CAD: {type: string}
            THB: {type: string}
            NZD: {type: string}
            CHF: {type: string}
            SEK: {type: string}
            NOK: {type: string}
            DKK: {type: string}
            PLN: {type: string}
            CZK: {type: string}
            HUF: {type: string}
            RON: {type: string}
            TRY: {type: string}
            BRL: {type: string}
            MXN: {type: string}
            ZAR: {type: string}
            ILS: {type: string}
            ISK: {type: string}
    ProductEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Product"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    SingleProductEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/Product"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    QuoteEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Quote"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    SingleQuoteEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/Quote"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    CatalogEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CatalogGroup"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    HistoryData:
      type: object
      required: [productId, points]
      properties:
        productId:
          type: string
        points:
          type: array
          items:
            $ref: "#/components/schemas/HistoryPoint"
    HistoryMeta:
      allOf:
        - $ref: "#/components/schemas/ApiMeta"
        - type: object
          required:
            [from, to, availableFrom, truncatedByAvailability, intervalMs]
          properties:
            from:
              type: [string, "null"]
              format: date-time
            to:
              type: [string, "null"]
              format: date-time
            requestedFrom:
              type: string
              format: date-time
            requestedTo:
              type: string
              format: date-time
            availableFrom:
              type: string
              format: date-time
            truncatedByAvailability:
              type: boolean
            intervalMs:
              type: integer
            historyReleaseId:
              type: string
            limit:
              type: integer
              minimum: 1
              maximum: 10000
            hasMore:
              type: boolean
            nextCursor:
              type: [string, "null"]
    HistoryEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/HistoryData"
        meta:
          $ref: "#/components/schemas/HistoryMeta"
    HourlyHistoryData:
      type: object
      required: [productId, granularity, months, points]
      properties:
        productId:
          type: string
        granularity:
          type: string
          const: 1h
        months:
          type: integer
          minimum: 1
          maximum: 36
        points:
          type: array
          items:
            $ref: "#/components/schemas/HistoryPoint"
    HourlyHistoryEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/HourlyHistoryData"
        meta:
          allOf:
            - $ref: "#/components/schemas/HistoryMeta"
            - type: object
              required: [months, granularity]
              properties:
                months:
                  type: integer
                  minimum: 1
                  maximum: 36
                granularity:
                  type: string
                  const: 1h
    Candle:
      type: object
      required:
        - time
        - epochMs
        - asOf
        - open
        - high
        - low
        - close
        - openUsd
        - highUsd
        - lowUsd
        - closeUsd
      properties:
        time:
          type: integer
          description: UTC bucket start as Unix seconds for chart clients
        epochMs:
          type: integer
        asOf:
          type: string
          format: date-time
        open:
          type: string
        high:
          type: string
        low:
          type: string
        close:
          type: string
        openUsd:
          type: string
        highUsd:
          type: string
        lowUsd:
          type: string
        closeUsd:
          type: string
    CandleData:
      type: object
      required:
        [productId, currency, unit, interval, months, availableFrom, candles]
      properties:
        productId:
          type: string
        currency:
          type: string
          const: CNY
        unit:
          type: string
          const: GPU_HOUR
        interval:
          type: string
          enum: [1h, 4h, 1d, 1w]
        months:
          type: integer
          minimum: 1
          maximum: 36
        availableFrom:
          type: string
          format: date-time
        candles:
          type: array
          items:
            $ref: "#/components/schemas/Candle"
    CandleEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/CandleData"
        meta:
          allOf:
            - $ref: "#/components/schemas/HistoryMeta"
            - type: object
              required: [months, interval]
              properties:
                months:
                  type: integer
                  minimum: 1
                  maximum: 36
                interval:
                  type: string
                  enum: [1h, 4h, 1d, 1w]
    ForwardIndexPoint:
      type: object
      description: One future UTC card-hour estimate in the active 720-hour index release
      required:
        [hourOffset, time, epochMs, asOf, priceUsd, priceCny, cumulativeUsd, cumulativeCny]
      properties:
        hourOffset:
          type: integer
          minimum: 1
          maximum: 720
        time:
          type: integer
          description: UTC hour as Unix seconds
        epochMs:
          type: integer
        asOf:
          type: string
          format: date-time
        priceUsd:
          type: string
        priceCny:
          type: string
        cumulativeUsd:
          type: string
        cumulativeCny:
          type: string
    ForwardIndexData:
      type: object
      description: |
        Snapshot-backed 720-hour rental-price outlook. The value is a constrained
        index projection, not a claim that a future rental transaction has occurred.
      required:
        - symbol
        - slug
        - name
        - gpuName
        - currency
        - unit
        - horizonHours
        - asOf
        - startsAt
        - endsAt
        - sourceSnapshotId
        - constituentCount
        - spotPriceUsd
        - spotPriceCny
        - averageHourlyPriceUsd
        - averageHourlyPriceCny
        - cumulativePriceUsd
        - cumulativePriceCny
        - projectedChangePct
        - points
      properties:
        symbol:
          type: string
          enum: [720-GB300, 720-B300, 720-B200, 720-H200, 720-H100]
        slug:
          type: string
          enum: [720-gb300, 720-b300, 720-b200, 720-h200, 720-h100]
        name:
          type: string
          enum: [720 GB300指数, 720 B300指数, 720 B200指数, 720 H200指数, 720 H100指数]
        gpuName:
          type: string
          enum: [GB300, B300, B200, H200, H100]
        currency:
          type: string
          const: CNY
        unit:
          type: string
          const: GPU_HOUR
        horizonHours:
          type: integer
          const: 720
        asOf:
          type: string
          format: date-time
        startsAt:
          type: string
          format: date-time
        endsAt:
          type: string
          format: date-time
        sourceSnapshotId:
          type: string
        constituentCount:
          type: integer
          minimum: 1
        spotPriceUsd:
          type: string
        spotPriceCny:
          type: string
        averageHourlyPriceUsd:
          type: string
        averageHourlyPriceCny:
          type: string
        cumulativePriceUsd:
          type: string
        cumulativePriceCny:
          type: string
        projectedChangePct:
          type: string
          description: 30-day outlook endpoint change relative to the current model baseline.
        points:
          type: array
          minItems: 720
          maxItems: 720
          items:
            $ref: "#/components/schemas/ForwardIndexPoint"
    ForwardIndexEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/ForwardIndexData"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    ForwardIndexListEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          minItems: 5
          maxItems: 5
          items:
            $ref: "#/components/schemas/ForwardIndexData"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    ForwardIndexCandle:
      type: object
      description: Historical OHLC candle for the 720-hour cumulative-cost index
      required:
        [time, epochMs, asOf, openUsd, highUsd, lowUsd, closeUsd, openCny, highCny, lowCny, closeCny]
      properties:
        time:
          type: integer
          description: UTC bucket timestamp as Unix seconds
        epochMs:
          type: integer
        asOf:
          type: string
          format: date-time
        openUsd: { type: string }
        highUsd: { type: string }
        lowUsd: { type: string }
        closeUsd: { type: string }
        openCny: { type: string }
        highCny: { type: string }
        lowCny: { type: string }
        closeCny: { type: string }
    ForwardIndexCandleEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [slug, interval, historyStart, asOf, candles]
          properties:
            slug:
              type: string
              enum: [720-gb300, 720-b300, 720-b200, 720-h200, 720-h100]
            interval:
              type: string
              enum: [1h, 4h, 1d, 1w]
            historyStart:
              type: string
              format: date-time
            asOf:
              type: string
              format: date-time
            candles:
              type: array
              items:
                $ref: "#/components/schemas/ForwardIndexCandle"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    Kai50HistoryPoint:
      type: object
      description: KAI 50 UTC daily OHLC candle
      required: [time, epochMs, asOf, open, high, low, close, value]
      properties:
        time:
          type: integer
          description: UTC timestamp as Unix seconds
        epochMs:
          type: integer
        asOf:
          type: string
          format: date-time
        value:
          type: string
          description: Alias of close for simple time-series clients
        open:
          type: string
        high:
          type: string
        low:
          type: string
        close:
          type: string
    Kai50Constituent:
      type: object
      required:
        - rank
        - id
        - gpuName
        - vram
        - gpuModel
        - region
        - formFactor
        - availableFrom
        - weightPct
        - representativePriceCny
        - regionCount
        - productCount
      properties:
        rank:
          type: integer
        id:
          type: string
        gpuName:
          type: string
        vram:
          type: [string, "null"]
        gpuModel:
          type: string
        region:
          type: string
        formFactor:
          type: [string, "null"]
        availableFrom:
          type: string
          format: date-time
        weightPct:
          type: string
        representativePriceCny:
          type: string
        regionCount:
          type: integer
        productCount:
          type: integer
    Kai50Changes:
      type: object
      required: [oneDayPct, thirtyDayPct, ninetyDayPct, yearToDatePct]
      properties:
        oneDayPct:
          type: string
        thirtyDayPct:
          type: string
        ninetyDayPct:
          type: string
        yearToDatePct:
          type: string
    Kai50Data:
      type: object
      required:
        - symbol
        - name
        - currency
        - value
        - asOf
        - baseDate
        - baseValue
        - historyStart
        - compositionAsOf
        - rebalanceFrequency
        - targetConstituentCount
        - constituentCount
        - changes
        - history
        - constituents
      properties:
        symbol:
          type: string
          const: KAI50
        name:
          type: string
          const: KAI 50 算力指数
        currency:
          type: string
          const: CNY
        value:
          type: string
        asOf:
          type: string
          format: date-time
        baseDate:
          type: string
          format: date-time
        baseValue:
          type: string
        historyStart:
          type: string
          format: date-time
        compositionAsOf:
          type: string
          format: date-time
        rebalanceFrequency:
          type: string
          const: monthly
        targetConstituentCount:
          type: integer
          const: 50
        constituentCount:
          type: integer
          description: Actual number of GPU card-hour product constituents in the index
        changes:
          $ref: "#/components/schemas/Kai50Changes"
        history:
          type: array
          items:
            $ref: "#/components/schemas/Kai50HistoryPoint"
        constituents:
          type: array
          items:
            $ref: "#/components/schemas/Kai50Constituent"
    Kai50Envelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/Kai50Data"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    CatalogSummary:
      type: object
      required: [id, gpuName, vram, displayName, productCount]
      properties:
        id:
          type: string
        gpuName:
          type: string
        vram:
          type: [string, "null"]
        displayName:
          type: string
        productCount:
          type: integer
    Metadata:
      type: object
      required:
        - currency
        - unit
        - formFactors
        - gpuModels
        - regions
        - catalogGroups
        - availabilityPolicy
        - updatedAt
      properties:
        currency:
          type: string
        unit:
          type: string
        formFactors:
          type: array
          items:
            type: string
            const: SXM
        gpuModels:
          type: array
          items:
            type: string
        regions:
          type: array
          items:
            type: string
        catalogGroups:
          type: array
          items:
            $ref: "#/components/schemas/CatalogSummary"
        availabilityPolicy:
          type: object
          required: [guaranteedForRentedHours, unavailableProductsPublished]
          properties:
            guaranteedForRentedHours:
              type: boolean
            unavailableProductsPublished:
              type: boolean
        updatedAt:
          type: string
          format: date-time
    MetadataEnvelope:
      type: object
      required: [data, meta]
      properties:
        data:
          $ref: "#/components/schemas/Metadata"
        meta:
          $ref: "#/components/schemas/ApiMeta"
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, requestId]
          properties:
            code:
              type: string
            message:
              type: string
            requestId:
              type: string
