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"
        - $ref: "#/components/parameters/MarketCategory"
        - $ref: "#/components/parameters/MarketSearch"
      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"
                        slug: will-the-most-popular-market-rank-first
                        question: "Will the most popular market rank first?"
                        category: other
                        categories:
                          - other
                        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/market-discovery:
    get:
      tags:
        - Public Markets
      summary: Get permanent market-hub sections
      description: Returns popular, closing-soon, discussed, probability-update, and recently resolved public market sections.
      operationId: getMarketDiscovery
      parameters:
        - $ref: "#/components/parameters/MarketCategory"
      responses:
        "200":
          description: Market discovery sections for the selected category or all categories.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketDiscoveryResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
  /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/{marketRef}:
    get:
      tags:
        - Public Markets
      summary: Get public market detail
      description: |
        Returns one public market by UUID or slug. 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: marketRef
          in: path
          required: true
          description: Market UUID or slug.
          schema:
            type: string
      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"
                      slug: will-the-newest-market-stay-visible
                      question: "Will the newest market stay visible?"
                      category: other
                      categories:
                        - other
                      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
                      creationSource: admin
                      description: "Public resolution rules."
                      sponsor: null
                      fxFamilyMember: null
        "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
    MarketCategory:
      name: category
      in: query
      required: false
      description: Category assignment. A market can appear in more than one category.
      schema:
        $ref: "#/components/schemas/MarketCategory"
    MarketSearch:
      name: q
      in: query
      required: false
      description: Case-insensitive search in the market question and description.
      schema:
        type: string
        maxLength: 100
  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
    MarketCategory:
      type: string
      enum:
        - ukraine
        - world
        - politics
        - economy
        - crypto
        - sports
        - technology
        - culture
        - other
    Resolution:
      type:
        - string
        - "null"
      enum:
        - YES
        - NO
        - CANCEL
        - null
    PublicMarketCommon:
      type: object
      required:
        - id
        - slug
        - question
        - category
        - categories
        - status
        - prob
        - volume
        - closeTime
        - resolution
        - createdAt
        - resolvedAt
        - lastActivityAt
        - traderCount
        - fills24h
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
        question:
          type: string
        category:
          $ref: "#/components/schemas/MarketCategory"
        categories:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: "#/components/schemas/MarketCategory"
        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
    PublicMarketListItem:
      allOf:
        - $ref: "#/components/schemas/PublicMarketCommon"
        - type: object
          required:
            - acceptingBets
          properties:
            acceptingBets:
              type: boolean
    MarketDiscoveryItem:
      allOf:
        - $ref: "#/components/schemas/PublicMarketListItem"
        - type: object
          required:
            - commentCount7d
            - probabilityChange
            - probabilityUpdatedAt
            - finalProbability
          properties:
            commentCount7d:
              type: integer
              minimum: 0
            probabilityChange:
              type: [number, "null"]
            probabilityUpdatedAt:
              type: [string, "null"]
              format: date-time
            finalProbability:
              type: [number, "null"]
              minimum: 0
              maximum: 1
    MarketDiscoveryResponse:
      type: object
      required:
        - category
        - counts
        - lastModified
        - openMarkets
        - closingSoonMarkets
        - discussedMarkets
        - probabilityUpdates
        - resolvedMarkets
      properties:
        category:
          anyOf:
            - $ref: "#/components/schemas/MarketCategory"
            - type: "null"
        counts:
          type: object
          required: [open, resolved, total]
          properties:
            open: { type: integer, minimum: 0 }
            resolved: { type: integer, minimum: 0 }
            total: { type: integer, minimum: 0 }
        lastModified:
          type: string
          format: date-time
        openMarkets:
          type: array
          maxItems: 12
          items: { $ref: "#/components/schemas/MarketDiscoveryItem" }
        closingSoonMarkets:
          type: array
          maxItems: 6
          items: { $ref: "#/components/schemas/MarketDiscoveryItem" }
        discussedMarkets:
          type: array
          maxItems: 6
          items: { $ref: "#/components/schemas/MarketDiscoveryItem" }
        probabilityUpdates:
          type: array
          maxItems: 6
          items: { $ref: "#/components/schemas/MarketDiscoveryItem" }
        resolvedMarkets:
          type: array
          maxItems: 6
          items: { $ref: "#/components/schemas/MarketDiscoveryItem" }
    PublicMarketDetailMarket:
      allOf:
        - $ref: "#/components/schemas/PublicMarketCommon"
        - type: object
          required:
            - creationSource
            - description
            - sponsor
            - fxFamilyMember
          properties:
            creationSource:
              type: string
              enum:
                - admin
                - user
            description:
              type:
                - string
                - "null"
            sponsor:
              anyOf:
                - $ref: "#/components/schemas/MarketSponsor"
                - type: "null"
            fxFamilyMember:
              anyOf:
                - $ref: "#/components/schemas/FxFamilyMember"
                - type: "null"
    MarketSponsor:
      type: object
      additionalProperties: false
      required:
        - name
        - subtitle
        - logoUrl
        - url
        - tone
      properties:
        name:
          type: string
        subtitle:
          type:
            - string
            - "null"
        logoUrl:
          type: string
          format: uri
        url:
          type: string
          format: uri
        tone:
          type: string
          enum:
            - slate
            - cyan
            - emerald
            - amber
            - rose
    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
    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
        - slug
        - question
        - category
        - categories
        - prob
        - volume
        - closeTime
        - createdAt
        - lastActivityAt
        - traderCount
        - fills24h
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        category:
          $ref: "#/components/schemas/MarketCategory"
        categories:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: "#/components/schemas/MarketCategory"
        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
      properties:
        market:
          $ref: "#/components/schemas/PublicMarketDetailMarket"
