openapi: 3.1.0
info:
  title: Commodity Fundamentals API
  version: 1.0.0
  description: |
    Unified REST API aggregating freely-available government commodity data
    (CFTC positioning, EIA energy, USDA agriculture) into a single
    developer-friendly interface with consistent formats and authentication.
  contact:
    url: https://commodityfundamentals.com
  license:
    name: Proprietary

servers:
  - url: https://commodityfundamentals.com/api/v1
    description: Production

security:
  - BearerAuth: []
  - ApiKeyQuery: []

tags:
  - name: Commodities
    description: Browse the catalog of available commodities
  - name: Time Series
    description: Retrieve historical time series data
  - name: CFTC
    description: Commitments of Traders report data

paths:
  /commodities:
    get:
      operationId: listCommodities
      tags: [Commodities]
      summary: List commodities
      description: Returns a paginated list of all available commodities with their metadata.
      parameters:
        - $ref: "#/components/parameters/Category"
        - name: search
          in: query
          description: Search commodities by name or symbol
          schema:
            type: string
        - $ref: "#/components/parameters/SortOrderAsc"
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: Paginated list of commodities
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CommoditySummary"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
              example:
                data:
                  - id: WTI_CRUDE_OIL
                    name: WTI Crude Oil
                    symbol: CL
                    category: energy
                    exchange: NYMEX
                    unit: USD/barrel
                meta:
                  page: 1
                  per_page: 25
                  total: 13
                  total_pages: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /commodities/{slug}:
    get:
      operationId: getCommodity
      tags: [Commodities]
      summary: Get commodity
      description: Returns detailed information for a single commodity, including all available data series and date ranges.
      parameters:
        - name: slug
          in: path
          required: true
          description: Commodity identifier (e.g., `WTI_CRUDE_OIL`)
          schema:
            type: string
            example: WTI_CRUDE_OIL
      responses:
        "200":
          description: Commodity detail
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/CommodityDetail"
              example:
                data:
                  id: WTI_CRUDE_OIL
                  name: WTI Crude Oil
                  symbol: CL
                  category: energy
                  exchange: NYMEX
                  unit: USD/barrel
                  description: West Texas Intermediate light sweet crude oil futures contract.
                  contract_size: "1,000 barrels"
                  tick_size: 0.01
                  available_series:
                    - type: price
                      frequency: daily
                      first_date: "1983-03-30"
                      last_date: "2024-06-21"
                    - type: cot_legacy
                      frequency: weekly
                      first_date: "1986-01-15"
                      last_date: "2024-06-18"
                    - type: cot_disaggregated
                      frequency: weekly
                      first_date: "2006-06-13"
                      last_date: "2024-06-18"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /commodities/{slug}/series:
    get:
      operationId: getSeries
      tags: [Time Series]
      summary: Get time series
      description: Returns historical time series data for a specific commodity and series type.
      parameters:
        - name: slug
          in: path
          required: true
          description: Commodity identifier (e.g., `WTI_CRUDE_OIL`)
          schema:
            type: string
            example: GOLD
        - name: type
          in: query
          required: true
          description: The type of data series to retrieve
          schema:
            type: string
            enum: [price, volume, open_interest, production, stocks, yield, planted_acres, harvested_acres, refinery_inputs, ending_stocks]
            example: price
        - name: start_date
          in: query
          description: Start date in ISO 8601 format. Defaults to 1 year ago.
          schema:
            type: string
            format: date
            example: "2024-01-01"
        - name: end_date
          in: query
          description: End date in ISO 8601 format. Defaults to today.
          schema:
            type: string
            format: date
        - name: frequency
          in: query
          description: Data frequency
          schema:
            type: string
            enum: [daily, weekly, monthly]
            default: daily
        - name: transform
          in: query
          description: Mathematical transform applied to the data
          schema:
            type: string
            enum: [none, change, pct_change, cumulative]
            default: none
        - name: limit
          in: query
          description: Maximum number of data points to return (max 10,000)
          schema:
            type: integer
            minimum: 1
            maximum: 10000
        - $ref: "#/components/parameters/SortOrderAsc"
      responses:
        "200":
          description: Time series data
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/TimeSeriesPoint"
                  meta:
                    $ref: "#/components/schemas/SeriesMeta"
              example:
                data:
                  - date: "2024-01-05"
                    value: 2044.50
                    open: 2062.98
                    high: 2078.40
                    low: 2024.10
                    close: 2044.50
                  - date: "2024-01-12"
                    value: 2049.70
                    open: 2046.80
                    high: 2062.30
                    low: 2017.30
                    close: 2049.70
                meta:
                  commodity_id: GOLD
                  type: price
                  frequency: weekly
                  unit: USD/troy oz
                  transform: none
                  count: 25
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /commodities/{slug}/cot:
    get:
      operationId: getCotReports
      tags: [CFTC]
      summary: Get COT reports
      description: Returns CFTC Commitments of Traders report data for a specific commodity contract.
      parameters:
        - name: slug
          in: path
          required: true
          description: Commodity identifier (e.g., `WTI_CRUDE_OIL`)
          schema:
            type: string
            example: WTI_CRUDE_OIL
        - name: format
          in: query
          description: Report format
          schema:
            type: string
            enum: [legacy, disaggregated]
            default: legacy
        - name: start_date
          in: query
          description: Start date in ISO 8601 format
          schema:
            type: string
            format: date
            example: "2024-01-01"
        - name: end_date
          in: query
          description: End date in ISO 8601 format
          schema:
            type: string
            format: date
        - $ref: "#/components/parameters/SortOrderDesc"
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: Paginated COT report data
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/CftcReport"
                  meta:
                    $ref: "#/components/schemas/PaginationMeta"
              example:
                data:
                  - report_date: "2024-06-18"
                    commodity: WTI_CRUDE_OIL
                    exchange: NYMEX
                    format: legacy
                    open_interest: 1823456
                    commercial:
                      long: 456789
                      short: 523456
                      spreading: 0
                      net: -66667
                    non_commercial:
                      long: 634567
                      short: 234567
                      spreading: 312345
                      net: 400000
                    non_reportable:
                      long: 107755
                      short: 140088
                      net: -32333
                meta:
                  page: 1
                  per_page: 25
                  total: 24
                  total_pages: 1
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: Pass your API key as a Bearer token in the Authorization header.
    ApiKeyQuery:
      type: apiKey
      in: query
      name: api_key
      description: Pass your API key as the `api_key` query parameter. Not recommended for production.

  parameters:
    Page:
      name: page
      in: query
      description: Page number (default 1)
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      description: Results per page, max 100 (default 25)
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    Category:
      name: category
      in: query
      description: Filter by commodity category
      schema:
        type: string
        enum: [energy, metals, agriculture, softs]
    SortOrderAsc:
      name: sort_order
      in: query
      description: Sort order for results (default asc)
      schema:
        type: string
        enum: [asc, desc]
        default: asc
    SortOrderDesc:
      name: sort_order
      in: query
      description: Sort order for results (default desc)
      schema:
        type: string
        enum: [asc, desc]
        default: desc

  headers:
    X-RateLimit-Limit:
      description: Maximum requests allowed per minute for your plan
      schema:
        type: integer
        example: 60
    X-RateLimit-Remaining:
      description: Requests remaining in current window
      schema:
        type: integer
        example: 45
    X-RateLimit-Reset:
      description: Unix timestamp when the rate limit window resets
      schema:
        type: integer
        example: 1719244800
    Retry-After:
      description: Seconds to wait before retrying (only on 429 responses)
      schema:
        type: integer
        example: 30

  schemas:
    CommoditySummary:
      type: object
      required: [id, name, category]
      properties:
        id:
          type: string
          description: Unique commodity identifier (screaming snake case)
          example: WTI_CRUDE_OIL
        name:
          type: string
          example: WTI Crude Oil
        symbol:
          type: string
          description: Exchange ticker symbol
          example: CL
        category:
          type: string
          enum: [energy, metals, agriculture, softs]
          example: energy
        exchange:
          type: string
          example: NYMEX
        unit:
          type: string
          example: USD/barrel

    CommodityDetail:
      type: object
      required: [id, name, category]
      properties:
        id:
          type: string
          example: WTI_CRUDE_OIL
        name:
          type: string
          example: WTI Crude Oil
        symbol:
          type: string
          example: CL
        category:
          type: string
          enum: [energy, metals, agriculture, softs]
        exchange:
          type: string
          example: NYMEX
        unit:
          type: string
          example: USD/barrel
        description:
          type: string
          example: West Texas Intermediate light sweet crude oil futures contract.
        contract_size:
          type: string
          example: "1,000 barrels"
        tick_size:
          type: number
          example: 0.01
        available_series:
          type: array
          items:
            type: object
            required: [type, frequency, first_date, last_date]
            properties:
              type:
                type: string
                enum: [price, volume, open_interest, cot_legacy, cot_disaggregated]
              frequency:
                type: string
                enum: [daily, weekly, monthly]
              first_date:
                type: string
                format: date
              last_date:
                type: string
                format: date

    TimeSeriesPoint:
      type: object
      required: [date, value]
      properties:
        date:
          type: string
          format: date
          example: "2024-01-05"
        value:
          type: number
          description: Primary value (close price, or transformed value)
          example: 2044.50
        open:
          type: number
          description: Opening price (price series only)
          example: 2062.98
        high:
          type: number
          description: High price (price series only)
          example: 2078.40
        low:
          type: number
          description: Low price (price series only)
          example: 2024.10
        close:
          type: number
          description: Closing price (price series only)
          example: 2044.50
        volume:
          type: integer
          description: Trading volume (price series only)
          example: 182345

    SeriesMeta:
      type: object
      required: [commodity_id, type, frequency, transform, count, request_id]
      properties:
        commodity_id:
          type: string
          example: GOLD
        type:
          type: string
          enum: [price, volume, open_interest, production, stocks, yield, planted_acres, harvested_acres, refinery_inputs, ending_stocks]
        frequency:
          type: string
          enum: [daily, weekly, monthly]
        unit:
          type: string
          example: USD/troy oz
        transform:
          type: string
          enum: [none, change, pct_change, cumulative]
        count:
          type: integer
          description: Number of data points returned
          example: 25
        request_id:
          type: string
          description: Unique request identifier for support and debugging
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890

    CftcReport:
      type: object
      required: [report_date, commodity, format, open_interest]
      properties:
        report_date:
          type: string
          format: date
          example: "2024-06-18"
        commodity:
          type: string
          example: WTI_CRUDE_OIL
        exchange:
          type: string
          example: NYMEX
        format:
          type: string
          enum: [legacy, disaggregated]
        open_interest:
          type: integer
          example: 1823456
        commercial:
          $ref: "#/components/schemas/PositionGroup"
        non_commercial:
          $ref: "#/components/schemas/PositionGroupWithSpreading"
        non_reportable:
          $ref: "#/components/schemas/PositionGroupNet"
        producer_merchant:
          $ref: "#/components/schemas/PositionGroup"
        swap_dealer:
          $ref: "#/components/schemas/PositionGroupWithSpreading"
        managed_money:
          $ref: "#/components/schemas/PositionGroupWithSpreading"
        other_reportable:
          $ref: "#/components/schemas/PositionGroupWithSpreading"
      description: |
        Legacy format includes: commercial, non_commercial, non_reportable.
        Disaggregated format includes: producer_merchant, swap_dealer, managed_money, other_reportable.
        Fields for the other format will be absent.

    PositionGroup:
      type: object
      properties:
        long:
          type: integer
        short:
          type: integer
        net:
          type: integer
          description: Computed as long minus short

    PositionGroupWithSpreading:
      type: object
      properties:
        long:
          type: integer
        short:
          type: integer
        spreading:
          type: integer
        net:
          type: integer
          description: Computed as long minus short

    PositionGroupNet:
      type: object
      properties:
        long:
          type: integer
        short:
          type: integer
        net:
          type: integer
          description: Computed as long minus short

    PaginationMeta:
      type: object
      required: [page, per_page, total, total_pages, request_id]
      properties:
        page:
          type: integer
          example: 1
        per_page:
          type: integer
          example: 25
        total:
          type: integer
          description: Total number of records matching the query
          example: 142
        total_pages:
          type: integer
          example: 6
        request_id:
          type: string
          description: Unique request identifier for support and debugging
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, doc_url, request_id]
          properties:
            code:
              type: string
              description: Machine-readable error code
              example: invalid_parameter
            message:
              type: string
              description: Human-readable error description
              example: "The 'start_date' parameter must be in ISO 8601 format (YYYY-MM-DD)."
            doc_url:
              type: string
              format: uri
              description: Link to documentation for this error code
              example: "https://commodityfundamentals.com/docs/errors#invalid_parameter"
            param:
              type: string
              description: The parameter that caused the error (if applicable)
              example: start_date
            request_id:
              type: string
              description: Unique request identifier for support
              example: a1b2c3d4-e5f6-7890-abcd-ef1234567890

  responses:
    BadRequest:
      description: Invalid request or parameter
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: invalid_parameter
              message: "The 'start_date' parameter must be in ISO 8601 format (YYYY-MM-DD)."
              param: start_date
              request_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: unauthorized
              message: Missing or invalid API key.
              request_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: forbidden
              message: Your API key does not have access to this resource.
              request_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: not_found
              message: "Commodity 'INVALID' not found."
              request_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: rate_limited
              message: You have exceeded your rate limit. Try again in 30 seconds.
              request_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
