openapi: 3.1.0
info:
  title: BlueChip Fantasy Public API
  version: 1.0.0
  summary: Read-only, keyless JSON for college, Devy, C2C and NFL fantasy leagues.
  description: |
    Read-only JSON over BlueChip Fantasy league data.
    Guide: https://playbluechip.com/developers

    - No keys. Stay under 1,000 requests per minute per IP.
    - Players are source-qualified: "college:48213", "nfl:4034". NFL players
      carry `sleeper_id` in the players list.
    - Rosters are numbered 1..N per league (`roster_id`).
    - League endpoints describe the league's current season.
    - A missing resource is 404 with the body `null`.
    - Fetch the players lists at most daily and honour ETags.
servers:
  - url: https://api.playbluechip.com/v1
security: []

components:
  parameters:
    Sport:
      name: sport
      in: path
      required: true
      schema: { type: string, enum: [college, nfl] }
    LeagueId:
      name: league_id
      in: path
      required: true
      schema: { type: string, format: uuid }
    DraftId:
      name: draft_id
      in: path
      required: true
      schema: { type: string, format: uuid }
    UserId:
      name: user_id
      in: path
      required: true
      schema: { type: string, format: uuid }
    Season:
      name: season
      in: path
      required: true
      schema: { type: integer, minimum: 2000, maximum: 2100 }
    IfNoneMatch:
      name: If-None-Match
      in: header
      required: false
      schema: { type: string }

  headers:
    ETag: { schema: { type: string } }
    AsOf:
      description: When the body was built (ISO 8601).
      schema: { type: string, format: date-time }
    CacheControl:
      description: "public, max-age=<left>, s-maxage=<left>, stale-while-revalidate=<2×left>, where <left> is the seconds remaining of the body's freshness."
      schema: { type: string }
    RateLimitLimit: { description: Requests allowed per minute per IP., schema: { type: integer } }
    RateLimitRemaining: { schema: { type: integer } }
    RetryAfter: { schema: { type: integer } }

  responses:
    NotModified:
      description: The ETag matched.
    NotFound:
      description: No such resource. The body is the JSON literal `null`.
      content:
        application/json:
          schema: { type: 'null' }
    BadRequest:
      description: Invalid path or query value.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: More than the per-IP limit this minute.
      headers:
        Retry-After: { $ref: '#/components/headers/RetryAfter' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Busy:
      description: The service is at capacity; retry after the given delay.
      headers:
        Retry-After: { $ref: '#/components/headers/RetryAfter' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string, examples: [invalid_week, invalid_sport, rate_limited, too_many_uncached, busy] }
            message: { type: string }

    PlayerId:
      type: string
      pattern: '^(college|nfl):\d+$'
      examples: ['college:48213', 'nfl:4034']

    State:
      type: object
      properties:
        sport: { type: string, enum: [college, nfl] }
        week: { type: integer }
        leg: { type: integer }
        display_week: { type: integer }
        season: { type: string }
        league_season: { type: string }
        league_create_season: { type: string }
        previous_season: { type: string }
        season_type: { type: string, enum: [pre, regular, off] }
        season_start_date: { type: [string, 'null'], format: date }

    Player:
      type: object
      description: Identity only. No stats, injuries, depth charts, projections, ADP or provider ids.
      properties:
        player_id: { $ref: '#/components/schemas/PlayerId' }
        source_id: { type: string }
        sport: { type: string, enum: [college, nfl] }
        first_name: { type: string }
        last_name: { type: string }
        full_name: { type: string }
        search_first_name: { type: string }
        search_last_name: { type: string }
        search_full_name: { type: string }
        position: { type: [string, 'null'] }
        fantasy_positions: { type: array, items: { type: string } }
        team: { type: [string, 'null'], description: 'College teams-table abbreviation, or NFL team.' }
        number: { type: [integer, 'null'] }
        height: { type: [string, 'null'], description: Inches. }
        weight: { type: [string, 'null'], description: Pounds. }
        active: { type: boolean }
        status: { type: [string, 'null'] }
        school: { type: [string, 'null'], description: College only. }
        class: { type: [string, 'null'], description: College only. }
        college: { type: [string, 'null'], description: NFL only. }
        years_exp: { type: [integer, 'null'], description: NFL only. }
        age: { type: [integer, 'null'], description: NFL only. }
        birth_date: { type: [string, 'null'], description: NFL only. }
        sleeper_id: { type: [string, 'null'], description: NFL only. }

    TrendingPlayer:
      type: object
      properties:
        player_id: { $ref: '#/components/schemas/PlayerId' }
        count: { type: integer, description: Distinct BlueChip leagues that made the move in the last 7 days. }
        pct: { type: number, description: count as a percentage of leagues whose pool can roster the player. }
        eligible_leagues: { type: integer }

    User:
      type: object
      properties:
        user_id: { type: string, format: uuid }
        username: { type: [string, 'null'] }
        display_name: { type: [string, 'null'] }
        avatar: { type: [string, 'null'], description: A full image URL. }
        is_bot: { type: boolean }

    LeagueUser:
      allOf:
        - $ref: '#/components/schemas/User'
        - type: object
          properties:
            league_id: { type: string, format: uuid }
            is_owner: { type: boolean, description: Commissioner or co-commissioner. }
            metadata:
              type: object
              properties:
                team_name: { type: string }

    League:
      type: object
      properties:
        league_id: { type: string, format: uuid }
        name: { type: string }
        sport: { type: string, enum: [college, nfl], description: The calendar the league scores on. }
        season: { type: string }
        season_type: { type: string, const: regular }
        status: { type: string, enum: [pre_draft, drafting, in_season, complete] }
        total_rosters: { type: integer }
        roster_positions:
          type: array
          items: { type: string }
          examples: [[QB, RB, RB, WR, WR, TE, FLEX, SUPER_FLEX, BN, BN]]
        scoring_settings:
          type: object
          additionalProperties: { type: number }
          description: Common fantasy stat keys where one exists (rec, pass_yd, pts_allow_7_13…); BlueChip's stat names otherwise.
        settings:
          type: object
          description: Integers, including rs_slots, devy_slots and the acquisition limits.
          additionalProperties: { type: integer }
          properties:
            type: { type: integer, description: '0 redraft, 2 dynasty.' }
            best_ball: { type: integer }
            num_teams: { type: integer }
            start_week: { type: integer }
            playoff_week_start: { type: integer }
            playoff_teams: { type: integer }
            playoff_round_type: { type: integer }
            league_average_match: { type: integer }
            reserve_slots: { type: integer }
            taxi_slots: { type: integer }
            rs_slots: { type: integer }
            devy_slots: { type: integer }
            waiver_type: { type: integer, description: '0 rolling, 1 reverse standings, 2 FAAB.' }
            waiver_budget: { type: integer }
            waiver_bid_min: { type: integer }
            waiver_clear_days: { type: integer }
            disable_trades: { type: integer }
            divisions: { type: integer }
            leg: { type: integer }
            last_scored_leg: { type: integer }
            season_acquisition_limit: { type: integer }
            matchup_acquisition_limit: { type: integer }
        metadata:
          type: object
          additionalProperties: { type: string }
          description: Each only when set. league_type, player_pool, matchup_style, consolation_mode, claim_mechanism, trade_deadline_at, trade_review_type, c2c_link_id, c2c_partner_league_id, division_1..N.
        previous_league_id: { type: 'null' }
        draft_id: { type: [string, 'null'], format: uuid }
        avatar: { type: [string, 'null'] }
        bracket_id: { type: 'null' }
        loser_bracket_id: { type: 'null' }

    Roster:
      type: object
      properties:
        roster_id: { type: integer }
        league_id: { type: string, format: uuid }
        owner_id: { type: [string, 'null'], format: uuid }
        co_owners: { type: [array, 'null'], items: { type: string, format: uuid } }
        players: { type: array, items: { $ref: '#/components/schemas/PlayerId' } }
        starters:
          type: array
          description: Aligned with the league's starting roster_positions; "0" is an empty slot.
          items: { type: string }
        reserve: { type: array, items: { $ref: '#/components/schemas/PlayerId' }, description: IR. }
        taxi: { type: array, items: { $ref: '#/components/schemas/PlayerId' } }
        rs: { type: array, items: { $ref: '#/components/schemas/PlayerId' }, description: BlueChip redshirt slots. }
        devy: { type: array, items: { $ref: '#/components/schemas/PlayerId' }, description: BlueChip Devy stash. }
        settings:
          type: object
          properties:
            wins: { type: integer }
            losses: { type: integer }
            ties: { type: integer }
            fpts: { type: integer }
            fpts_decimal: { type: integer }
            fpts_against: { type: integer }
            fpts_against_decimal: { type: integer }
            total_moves: { type: integer }
            waiver_position: { type: integer }
            division: { type: integer }
        metadata:
          type: object
          properties:
            team_name: { type: string }
            avatar_url: { type: string }
            roster_uuid: { type: string, format: uuid }
            c2c_pair_id: { type: string, format: uuid }

    Matchup:
      type: object
      properties:
        roster_id: { type: integer }
        matchup_id: { type: [integer, 'null'], description: Shared by opponents; null for a bye or total-points league. }
        points: { type: number }
        custom_points: { type: 'null' }
        starters: { type: array, items: { type: string } }
        players: { type: array, items: { $ref: '#/components/schemas/PlayerId' } }
        starters_points: { type: array, items: { type: number }, description: Only when per-player points are enabled. }
        players_points: { type: object, additionalProperties: { type: number }, description: Only when per-player points are enabled. }

    DraftPickRef:
      type: object
      properties:
        season: { type: [string, 'null'] }
        round: { type: integer }
        roster_id: { type: [integer, 'null'], description: Original owner. }
        previous_owner_id: { type: [integer, 'null'] }
        owner_id: { type: [integer, 'null'] }
        draft_category: { type: [string, 'null'], enum: [primary, devy, rookie, supplemental, rookie_supplemental, null] }

    TradedPick:
      allOf:
        - $ref: '#/components/schemas/DraftPickRef'
        - type: object
          properties:
            draft_id: { type: [string, 'null'], format: uuid }

    Transaction:
      type: object
      properties:
        transaction_id: { type: string }
        type: { type: string, enum: [free_agent, waiver, trade] }
        status: { type: string, const: complete }
        status_updated: { type: integer, description: Epoch ms. }
        created: { type: integer, description: Epoch ms. }
        creator: { type: [string, 'null'], format: uuid }
        leg: { type: integer }
        roster_ids: { type: array, items: { type: integer } }
        consenter_ids: { type: array, items: { type: integer } }
        adds: { type: [object, 'null'], additionalProperties: { type: integer }, description: player_id → receiving roster_id. }
        drops: { type: [object, 'null'], additionalProperties: { type: integer }, description: player_id → giving roster_id. }
        draft_picks: { type: array, items: { $ref: '#/components/schemas/DraftPickRef' } }
        waiver_budget: { type: array, items: { type: object } }
        settings: { type: 'null' }
        metadata: { type: 'null' }

    Draft:
      type: object
      properties:
        draft_id: { type: string, format: uuid }
        league_id: { type: string, format: uuid }
        season: { type: [string, 'null'] }
        season_type: { type: string }
        sport: { type: string, enum: [college, nfl] }
        type: { type: string, examples: [snake, linear] }
        status: { type: string, enum: [pre_draft, drafting, paused, complete] }
        start_time: { type: [integer, 'null'] }
        created: { type: [integer, 'null'] }
        last_picked: { type: [integer, 'null'] }
        settings:
          type: object
          properties:
            teams: { type: integer }
            rounds: { type: integer }
            pick_timer: { type: integer }
        metadata:
          type: object
          properties:
            name: { type: string }
            draft_category: { type: string }
            c2c_link_id: { type: string }
        draft_order: { type: [object, 'null'], additionalProperties: { type: integer }, description: user_id → seat. }
        slot_to_roster_id: { type: [object, 'null'], additionalProperties: { type: integer }, description: seat → roster_id. }
        creators: { type: 'null' }

    DraftPick:
      type: object
      properties:
        draft_id: { type: string, format: uuid }
        pick_no: { type: integer }
        round: { type: integer }
        draft_slot: { type: [integer, 'null'], description: The original owner's seat. }
        roster_id: { type: [integer, 'null'] }
        picked_by: { type: string }
        player_id: { $ref: '#/components/schemas/PlayerId' }
        is_keeper: { type: 'null' }
        auto_picked: { type: boolean }
        metadata:
          type: object
          properties:
            first_name: { type: string }
            last_name: { type: string }
            position: { type: string }
            team: { type: string }
            draft_category: { type: string }

paths:
  /state/{sport}:
    get:
      operationId: getState
      summary: Current week and season for a calendar
      parameters: [{ $ref: '#/components/parameters/Sport' }]
      responses:
        '200':
          description: OK
          headers:
            ETag: { $ref: '#/components/headers/ETag' }
            Cache-Control: { $ref: '#/components/headers/CacheControl' }
            X-RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }
            X-RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }
          content: { application/json: { schema: { $ref: '#/components/schemas/State' } } }
        '400': { $ref: '#/components/responses/BadRequest' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /players/{sport}:
    get:
      operationId: getPlayers
      summary: Every player in a pool, keyed by player_id (large; fetch at most daily)
      parameters: [{ $ref: '#/components/parameters/Sport' }, { $ref: '#/components/parameters/IfNoneMatch' }]
      responses:
        '200':
          description: OK
          headers: { ETag: { $ref: '#/components/headers/ETag' }, X-BlueChip-As-Of: { $ref: '#/components/headers/AsOf' } }
          content:
            application/json:
              schema: { type: object, additionalProperties: { $ref: '#/components/schemas/Player' } }
        '304': { $ref: '#/components/responses/NotModified' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /players/{sport}/trending/{type}:
    get:
      operationId: getTrendingPlayers
      summary: Most added, dropped or traded players across BlueChip leagues (7 days)
      parameters:
        - $ref: '#/components/parameters/Sport'
        - { name: type, in: path, required: true, schema: { type: string, enum: [add, drop, trade] } }
        - { name: lookback_hours, in: query, description: Accepted and ignored; the window is always 7 days., schema: { type: integer } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 25 } }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/TrendingPlayer' } } } }
        '400': { $ref: '#/components/responses/BadRequest' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /user/{user_ref}:
    get:
      operationId: getUser
      summary: A user by username (case-insensitive) or user_id
      parameters: [{ name: user_ref, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/User' } } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /user/{user_id}/leagues/{sport}/{season}:
    get:
      operationId: getUserLeagues
      summary: A user's leagues on a calendar in a season (up to 25; current or previous season only, otherwise 404)
      parameters:
        - $ref: '#/components/parameters/UserId'
        - $ref: '#/components/parameters/Sport'
        - $ref: '#/components/parameters/Season'
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/League' } } } } }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /user/{user_id}/drafts/{sport}/{season}:
    get:
      operationId: getUserDrafts
      summary: Drafts in a season across up to 25 of a user's leagues on a calendar (current or previous season only, otherwise 404)
      parameters:
        - $ref: '#/components/parameters/UserId'
        - $ref: '#/components/parameters/Sport'
        - $ref: '#/components/parameters/Season'
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Draft' } } } } }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /league/{league_id}:
    get:
      operationId: getLeague
      summary: A league's current season and settings
      parameters: [{ $ref: '#/components/parameters/LeagueId' }, { $ref: '#/components/parameters/IfNoneMatch' }]
      responses:
        '200':
          description: OK
          headers: { ETag: { $ref: '#/components/headers/ETag' }, X-BlueChip-As-Of: { $ref: '#/components/headers/AsOf' } }
          content: { application/json: { schema: { $ref: '#/components/schemas/League' } } }
        '304': { $ref: '#/components/responses/NotModified' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /league/{league_id}/rosters:
    get:
      operationId: getLeagueRosters
      summary: Rosters with lineups, reserves, record and waiver state
      parameters: [{ $ref: '#/components/parameters/LeagueId' }, { $ref: '#/components/parameters/IfNoneMatch' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Roster' } } } } }
        '304': { $ref: '#/components/responses/NotModified' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /league/{league_id}/users:
    get:
      operationId: getLeagueUsers
      summary: Owners, co-owners and commissioners
      parameters: [{ $ref: '#/components/parameters/LeagueId' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/LeagueUser' } } } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /league/{league_id}/matchups/{week}:
    get:
      operationId: getLeagueMatchups
      summary: One row per roster for a week (college weeks 1-13, NFL 1-18)
      parameters:
        - $ref: '#/components/parameters/LeagueId'
        - { name: week, in: path, required: true, schema: { type: integer, minimum: 1, maximum: 18 } }
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Matchup' } } } } }
        '304': { $ref: '#/components/responses/NotModified' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /league/{league_id}/transactions/{round}:
    get:
      operationId: getLeagueTransactions
      summary: A week's completed transactions, newest first
      parameters:
        - $ref: '#/components/parameters/LeagueId'
        - { name: round, in: path, required: true, schema: { type: integer, minimum: 1, maximum: 25 } }
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Transaction' } } } } }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /league/{league_id}/traded_picks:
    get:
      operationId: getLeagueTradedPicks
      summary: Unused picks owned by a roster other than the original
      parameters: [{ $ref: '#/components/parameters/LeagueId' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/TradedPick' } } } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /league/{league_id}/drafts:
    get:
      operationId: getLeagueDrafts
      summary: Every draft for the league, newest first
      parameters: [{ $ref: '#/components/parameters/LeagueId' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Draft' } } } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /draft/{draft_id}:
    get:
      operationId: getDraft
      summary: A draft
      parameters: [{ $ref: '#/components/parameters/DraftId' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Draft' } } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /draft/{draft_id}/picks:
    get:
      operationId: getDraftPicks
      summary: Picks made so far, in order
      parameters: [{ $ref: '#/components/parameters/DraftId' }, { $ref: '#/components/parameters/IfNoneMatch' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/DraftPick' } } } } }
        '304': { $ref: '#/components/responses/NotModified' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }

  /draft/{draft_id}/traded_picks:
    get:
      operationId: getDraftTradedPicks
      summary: Every traded pick in a draft, used or not
      parameters: [{ $ref: '#/components/parameters/DraftId' }]
      responses:
        '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/TradedPick' } } } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Busy' }
