openapi: 3.1.0
info:
  title: Plura Market Public Markets API
  version: 1.0.0
  summary: Public REST API for Plura Market prediction markets.
  description: |
    Unauthenticated read-only endpoints for public Plura Market market data.
    Responses include approved public market data and omit user-private,
    moderation, admin, and premium-only fields.
  contact:
    name: Plura Market
    url: https://pluramarket.com
servers:
  - url: https://pluramarket.com
    description: Production API origin
  - url: http://localhost:3051
    description: Local MVP API server
tags:
  - name: Public Markets
    description: Public market listing, snapshot, and detail endpoints.
paths:
  /v1/public/markets:
    get:
      tags:
        - Public Markets
      summary: List public markets
      description: |
        Returns approved and listed public markets. Public FX-family member
        markets are intentionally excluded from this regular list and can be
        fetched directly by ID when their family exposes the market.
      operationId: listPublicMarkets
      parameters:
        - $ref: "#/components/parameters/PublicMarketStatus"
        - $ref: "#/components/parameters/PublicMarketSort"
        - $ref: "#/components/parameters/PageLimit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Public market page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicMarketListResponse"
              examples:
                openMarkets:
                  summary: Open markets
                  value:
                    items:
                      - id: "00000000-0000-0000-0000-000000000104"
                        question: "Will the most popular market rank first?"
                        status: open
                        prob: 0.48
                        volume: 180
                        closeTime: "2026-06-05T12:00:00.000Z"
                        resolution: null
                        createdAt: "2026-05-24T12:00:00.000Z"
                        resolvedAt: null
                        lastActivityAt: "2026-05-30T11:05:00.000Z"
                        traderCount: 3
                        fills24h: 2
                        acceptingBets: true
                    nextCursor: null
        "400":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
  /v1/public/markets/snapshot:
    get:
      tags:
        - Public Markets
      summary: Get public market homepage snapshot
      description: Returns small open-market groups used by the public landing experience.
      operationId: getPublicMarketsSnapshot
      responses:
        "200":
          description: Fresh and closing-soon market groups.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicMarketsSnapshotResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
  /v1/public/markets/{marketId}:
    get:
      tags:
        - Public Markets
      summary: Get public market detail
      description: |
        Returns one public market with public order book, recent fills, and
        probability history. Hidden, pending, rejected, and non-public markets
        return 404. Public FX-family member markets are available here even when
        excluded from the regular market list.
      operationId: getPublicMarketDetail
      parameters:
        - name: marketId
          in: path
          required: true
          description: Market UUID.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Public market detail.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicMarketDetailResponse"
              examples:
                detail:
                  summary: Open market detail
                  value:
                    market:
                      id: "00000000-0000-0000-0000-000000000102"
                      question: "Will the newest market stay visible?"
                      status: open
                      prob: 0.62
                      volume: 145
                      closeTime: "2026-06-02T12:00:00.000Z"
                      resolution: null
                      createdAt: "2026-05-30T10:00:00.000Z"
                      resolvedAt: null
                      lastActivityAt: "2026-05-30T11:55:00.000Z"
                      traderCount: 2
                      fills24h: 1
                      acceptingBets: true
                      description: "Public resolution rules."
                      poolYes: 120
                      poolNo: 80
                      tradingPauseReason: null
                      fxFamilyMember: null
                    orderBook:
                      bids:
                        - price: 0.61
                          amount: 20
                      asks: []
                    recentFills:
                      - id: "30000000-0000-0000-0000-000000000001"
                        amount: 8
                        shares: 12
                        price: 0.62
                        timestamp: "2026-05-30T11:55:00.000Z"
                    probHistory:
                      - timestamp: "2026-05-30T10:00:00.000Z"
                        prob: 0.64
                      - timestamp: "2026-05-30T11:55:00.000Z"
                        prob: 0.62
        "400":
          $ref: "#/components/responses/ValidationError"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
components:
  parameters:
    PublicMarketStatus:
      name: status
      in: query
      required: false
      description: |
        Lifecycle filter. `archive` includes `closed`, `resolved`, and
        `cancelled` markets.
      schema:
        type: string
        enum:
          - open
          - archive
          - closed
          - resolved
          - cancelled
          - all
        default: open
    PublicMarketSort:
      name: sort
      in: query
      required: false
      description: |
        Sort order. Defaults to `popular` for `open` and `all`, and `ended` for
        archive-style statuses.
      schema:
        type: string
        enum:
          - popular
          - activity
          - volume
          - new
          - ended
    PageLimit:
      name: limit
      in: query
      required: false
      description: Page size.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque cursor returned as `nextCursor`. Reuse it with the same status and sort.
      schema:
        type: string
  responses:
    ValidationError:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error: Validation failed
            details:
              - path:
                  - limit
                message: Number must be less than or equal to 100
    NotFound:
      description: Market was not found or is not publicly visible.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error: Market not found
    RateLimited:
      description: Request rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error: Too Many Requests
  schemas:
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: string
        details:
          description: Optional validation or diagnostic details.
    MarketStatus:
      type: string
      enum:
        - open
        - closed
        - resolved
        - cancelled
    Resolution:
      type:
        - string
        - "null"
      enum:
        - YES
        - NO
        - CANCEL
        - null
    PublicMarketListItem:
      type: object
      required:
        - id
        - question
        - status
        - prob
        - volume
        - closeTime
        - resolution
        - createdAt
        - resolvedAt
        - lastActivityAt
        - traderCount
        - fills24h
        - acceptingBets
      properties:
        id:
          type: string
          format: uuid
        question:
          type: string
        status:
          $ref: "#/components/schemas/MarketStatus"
        prob:
          type: number
          minimum: 0
          maximum: 1
        volume:
          type: number
          minimum: 0
        closeTime:
          type: string
          format: date-time
        resolution:
          $ref: "#/components/schemas/Resolution"
        createdAt:
          type: string
          format: date-time
        resolvedAt:
          type:
            - string
            - "null"
          format: date-time
        lastActivityAt:
          type: string
          format: date-time
        traderCount:
          type: integer
          minimum: 0
        fills24h:
          type: integer
          minimum: 0
        acceptingBets:
          type: boolean
    PublicMarketDetailMarket:
      allOf:
        - $ref: "#/components/schemas/PublicMarketListItem"
        - type: object
          required:
            - description
            - poolYes
            - poolNo
            - tradingPauseReason
            - fxFamilyMember
          properties:
            description:
              type:
                - string
                - "null"
            poolYes:
              type: number
              minimum: 0
            poolNo:
              type: number
              minimum: 0
            tradingPauseReason:
              type:
                - string
                - "null"
            fxFamilyMember:
              anyOf:
                - $ref: "#/components/schemas/FxFamilyMember"
                - type: "null"
    FxFamilyMember:
      type: object
      additionalProperties: false
      required:
        - title
        - instrument
        - source
        - weekStart
        - weekEnd
        - thresholdValue
        - direction
      properties:
        title:
          type: string
        instrument:
          type: string
        source:
          type: string
        weekStart:
          type: string
          format: date
        weekEnd:
          type: string
          format: date
        thresholdValue:
          type: number
        direction:
          type: string
          enum:
            - gte
            - lte
    OrderBook:
      type: object
      additionalProperties: false
      required:
        - bids
        - asks
      properties:
        bids:
          type: array
          items:
            $ref: "#/components/schemas/OrderBookLevel"
        asks:
          type: array
          items:
            $ref: "#/components/schemas/OrderBookLevel"
    OrderBookLevel:
      type: object
      additionalProperties: false
      required:
        - price
        - amount
      properties:
        price:
          type: number
          minimum: 0.01
          maximum: 0.99
        amount:
          type: number
          minimum: 0
    PublicRecentFill:
      type: object
      additionalProperties: false
      required:
        - id
        - amount
        - shares
        - price
        - timestamp
      properties:
        id:
          type: string
          format: uuid
        amount:
          type: number
          minimum: 0
        shares:
          type: number
          minimum: 0
        price:
          type:
            - number
            - "null"
          minimum: 0.01
          maximum: 0.99
        timestamp:
          type: string
          format: date-time
    ProbabilityPoint:
      type: object
      additionalProperties: false
      required:
        - timestamp
        - prob
      properties:
        timestamp:
          type: string
          format: date-time
        prob:
          type: number
          minimum: 0
          maximum: 1
    PublicMarketListResponse:
      type: object
      additionalProperties: false
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/PublicMarketListItem"
        nextCursor:
          type:
            - string
            - "null"
    PublicMarketsSnapshotResponse:
      type: object
      additionalProperties: false
      required:
        - freshMarkets
        - closingSoonMarkets
      properties:
        freshMarkets:
          type: array
          items:
            $ref: "#/components/schemas/PublicMarketSignal"
        closingSoonMarkets:
          type: array
          items:
            $ref: "#/components/schemas/PublicMarketSignal"
    PublicMarketSignal:
      type: object
      additionalProperties: false
      required:
        - id
        - question
        - prob
        - volume
        - closeTime
        - createdAt
        - lastActivityAt
        - traderCount
        - fills24h
      properties:
        id:
          type: string
          format: uuid
        question:
          type: string
        prob:
          type: number
          minimum: 0
          maximum: 1
        volume:
          type: number
          minimum: 0
        closeTime:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        lastActivityAt:
          type: string
          format: date-time
        traderCount:
          type: integer
          minimum: 0
        fills24h:
          type: integer
          minimum: 0
    PublicMarketDetailResponse:
      type: object
      additionalProperties: false
      required:
        - market
        - orderBook
        - recentFills
        - probHistory
      properties:
        market:
          $ref: "#/components/schemas/PublicMarketDetailMarket"
        orderBook:
          $ref: "#/components/schemas/OrderBook"
        recentFills:
          type: array
          items:
            $ref: "#/components/schemas/PublicRecentFill"
        probHistory:
          type: array
          items:
            $ref: "#/components/schemas/ProbabilityPoint"
