openapi: 3.1.0
info:
  title: Scorelytics REST API
  version: 1.0.0
  summary: Real-time sports data — leagues, matches, statistics, lineups, standings.
  description: >
    The Scorelytics REST API exposes everything you need to build a live-score

    product, a fantasy app, an analytics dashboard or a sportsbook frontend.


    ### Base URL

    ```

    https://api.scorelytics.pro

    ```


    ### Authentication

    All endpoints under `/v1/{sport}/*` (where `sport` is `football` or
    `basketball`) require an API key. Pass it as

    `X-API-Key`, as a Bearer token, or as the `?api_key=` query parameter.

    See [Authentication](/getting-started/authentication) for details.


    ### Conventions

    - Times are Unix epoch seconds (UTC) unless otherwise noted.

    - All responses are JSON unless explicitly noted.

    - Pagination uses an opaque `next_cursor`; pass it back as `?after=`.


    ### Need help?

    Reach us at [support@scorelytics.pro](mailto:support@scorelytics.pro) or

    open a ticket from the [dashboard](https://app.scorelytics.pro).
  termsOfService: https://scorelytics.pro/terms
  contact:
    name: Scorelytics Support
    email: support@scorelytics.pro
    url: https://scorelytics.pro/support
  license:
    name: Commercial
    url: https://scorelytics.pro/terms
servers:
  - url: https://api.scorelytics.pro
    description: Production
  - url: https://staging-api.scorelytics.pro
    description: Staging
tags:
  - name: Matches
    description: |
      Live and historical matches. Includes scores, period/minute, events,
      statistics, lineups and standings snapshots.
  - name: Leagues
    description: Tracked competitions and their current standings.
  - name: System
    description: Health and readiness probes for monitoring.
security:
  - ApiKeyHeader: []
  - ApiKeyQuery: []
  - BearerAuth: []
paths:
  /v1/{sport}/matches:
    get:
      tags:
        - Matches
      summary: List matches
      operationId: listMatches
      description: >
        Returns a paginated list of matches. Combine filters to narrow the

        result set. The list is sorted by kickoff time ascending.


        **Tip** — for a daily live scoreboard, call `?status=live` and

        re-poll every 5–10 seconds, or use the [WebSocket
        stream](/websocket/all-live)

        for push updates.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - name: status
          in: query
          description: Filter by lifecycle status.
          required: false
          schema:
            type: string
            enum:
              - live
              - scheduled
              - finished
        - name: league_id
          in: query
          description: Restrict results to a single league.
          required: false
          schema:
            type: string
        - name: date
          in: query
          description: Filter by kickoff date in **UTC** (`YYYY-MM-DD`).
          required: false
          schema:
            type: string
            format: date
            example: '2026-05-04'
        - name: after
          in: query
          description: |
            Cursor for pagination. Pass the `next_cursor` value returned by a
            previous call to fetch the next page.
          required: false
          schema:
            type: integer
            format: int64
        - name: limit
          in: query
          description: Page size, between 1 and 200.
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Match'
                  total:
                    type: integer
                    description: Number of items in `data`.
                  limit:
                    type: integer
                    description: Echo of the page size used.
                  next_cursor:
                    type: integer
                    nullable: true
                    description: |
                      Pass as `?after=` to fetch the next page. Absent or null
                      when there are no more results.
              examples:
                live:
                  summary: Two live matches
                  value:
                    data:
                      - id: abc123
                        league_id: DEf456
                        season: 2025/26
                        home_team: Real Madrid
                        away_team: Barcelona
                        home_score: 2
                        away_score: 1
                        status_code: 2
                        minute: 67
                        kickoff_ts: 1746369000
                        updated_at: '2026-05-04T16:32:11Z'
                        home_logo: https://cdn.scorelytics.pro/teams/real-madrid.png
                        away_logo: https://cdn.scorelytics.pro/teams/barcelona.png
                      - id: xyz789
                        league_id: UvW111
                        season: 2025/26
                        home_team: Liverpool
                        away_team: Arsenal
                        home_score: 0
                        away_score: 0
                        status_code: 2
                        minute: 12
                        kickoff_ts: 1746367200
                        updated_at: '2026-05-04T16:32:09Z'
                        home_logo: null
                        away_logo: null
                    total: 2
                    limit: 50
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/matches/{id}:
    get:
      tags:
        - Matches
      summary: Get a match
      operationId: getMatch
      description: |
        Returns the full record for a match, including referee, venue,
        attendance, formations and half-time scores.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - $ref: '#/components/parameters/MatchId'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/matches/{id}/events:
    get:
      tags:
        - Matches
      summary: Get match events
      operationId: getMatchEvents
      description: |
        Chronological event timeline for a match: goals, cards, substitutions,
        penalties and own goals. Sorted by `minute` then `added_time`.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - $ref: '#/components/parameters/MatchId'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - match_id
                  - events
                properties:
                  match_id:
                    type: string
                  events:
                    type: array
                    items:
                      $ref: '#/components/schemas/MatchEvent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/matches/{id}/stats:
    get:
      tags:
        - Matches
      summary: Get match statistics
      operationId: getMatchStats
      description: |
        Returns the live in-game statistics for a match — possession, shots,
        corners, fouls, offsides, saves, etc. for football and FG%, 3P%, FT%,
        rebounds, assists and turnovers for basketball.

        The `stats` field is a JSON pass-through whose shape varies per sport.
        See [Statistics shape](/concepts/matches#statistics-shape) for the
        canonical field reference.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - $ref: '#/components/parameters/MatchId'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - match_id
                  - stats
                properties:
                  match_id:
                    type: string
                  stats:
                    description: Sport-aware statistics payload (see schema docs).
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/matches/{id}/lineups:
    get:
      tags:
        - Matches
      summary: Get match lineups
      operationId: getMatchLineups
      description: |
        Starting XI and bench for both teams, ordered home first, starters
        before substitutes.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - $ref: '#/components/parameters/MatchId'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - match_id
                  - lineups
                properties:
                  match_id:
                    type: string
                  lineups:
                    type: array
                    items:
                      $ref: '#/components/schemas/LineupPlayer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/matches/{id}/standings:
    get:
      tags:
        - Matches
      summary: Get standings snapshot for a match
      operationId: getMatchStandings
      description: |
        Returns the league table as it stood at this match's kickoff. Useful
        for retrospective analysis. For the *current* league standings, use
        `/v1/{sport}/leagues/{id}/standings`.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - $ref: '#/components/parameters/MatchId'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - match_id
                  - table
                properties:
                  match_id:
                    type: string
                  table:
                    description: Standings table (raw provider format).
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/matches/{id}/h2h:
    get:
      tags:
        - Matches
      summary: Get head-to-head history
      operationId: getMatchH2H
      description: |
        Recent meetings between the two teams of the requested match.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - $ref: '#/components/parameters/MatchId'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                description: Head-to-head payload (raw provider format).
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/leagues:
    get:
      tags:
        - Leagues
      summary: List leagues
      operationId: listLeagues
      description: All competitions tracked on your account, ordered by country and name.
      parameters:
        - $ref: '#/components/parameters/SportParam'
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/League'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/{sport}/leagues/{id}/standings:
    get:
      tags:
        - Leagues
      summary: Get current league standings
      operationId: getLeagueStandings
      description: |
        Returns the latest standings snapshot for a league. Standings are
        recomputed after every match in that league.
      parameters:
        - $ref: '#/components/parameters/SportParam'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                type: object
                required:
                  - league_id
                  - standings
                properties:
                  league_id:
                    type: string
                  standings:
                    description: Latest standings table (raw provider format).
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '400':
          description: Missing league id in path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /health:
    get:
      tags:
        - System
      summary: Liveness probe
      operationId: liveness
      security: []
      description: |
        Always returns `200`, even when downstream components are degraded.
        Use this for load balancer liveness checks.
      responses:
        '200':
          description: Service is alive.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatus'
  /readyz:
    get:
      tags:
        - System
      summary: Readiness probe
      operationId: readiness
      security: []
      description: |
        Returns `200` only when every downstream component (Postgres, Redis,
        NATS) is healthy. Returns `503` otherwise. Use this to gate routing
        decisions at the load balancer.
      responses:
        '200':
          description: All components ready.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatus'
        '503':
          description: One or more components are unhealthy.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatus'
components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'Send your API key as `X-API-Key: sk_live_...`.'
    ApiKeyQuery:
      type: apiKey
      in: query
      name: api_key
      description: |
        Send your API key as a query parameter. Use only when headers are not
        an option (e.g. WebSocket connections).
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: 'Send your API key as `Authorization: Bearer sk_live_...`.'
  parameters:
    SportParam:
      name: sport
      in: path
      required: true
      description: Sport identifier — `football` or `basketball`.
      schema:
        type: string
        enum:
          - football
          - basketball
    MatchId:
      name: id
      in: path
      required: true
      description: Match identifier returned by `listMatches`.
      schema:
        type: string
  responses:
    Unauthorized:
      description: API key is missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: missing or invalid api key
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Limit:
          schema:
            type: integer
        X-RateLimit-Remaining:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Match:
      type: object
      description: Compact match record returned by list endpoints.
      required:
        - id
        - league_id
        - home_team
        - away_team
        - home_score
        - away_score
        - status_code
        - kickoff_ts
        - updated_at
      properties:
        id:
          type: string
          description: Stable match identifier.
        league_id:
          type: string
        season:
          type: string
          nullable: true
          example: 2025/26
        home_team:
          type: string
        away_team:
          type: string
        home_score:
          type: integer
          minimum: 0
        away_score:
          type: integer
          minimum: 0
        status_code:
          type: integer
          description: See [Status Codes](/concepts/status-codes).
          example: 2
        minute:
          type: integer
          nullable: true
          description: Live wall-clock minute (football). `null` outside live play.
        kickoff_ts:
          type: integer
          format: int64
          description: Unix epoch seconds (UTC).
        updated_at:
          type: string
          format: date-time
        home_logo:
          type: string
          format: uri
          nullable: true
        away_logo:
          type: string
          format: uri
          nullable: true
    MatchDetail:
      type: object
      description: Full match record. Superset of `Match`.
      required:
        - id
        - league_id
        - home_team
        - away_team
        - home_score
        - away_score
        - status_code
        - kickoff_ts
        - updated_at
      properties:
        id:
          type: string
        league_id:
          type: string
        season:
          type: string
          nullable: true
        home_team:
          type: string
        away_team:
          type: string
        home_score:
          type: integer
        away_score:
          type: integer
        home_ht_score:
          type: integer
          nullable: true
          description: Home half-time score.
        away_ht_score:
          type: integer
          nullable: true
        status_code:
          type: integer
        period:
          type: integer
          nullable: true
          description: See [Period Codes](/concepts/period-codes).
        minute:
          type: integer
          nullable: true
        added_time:
          type: integer
          nullable: true
        kickoff_ts:
          type: integer
          format: int64
        finished_at:
          type: string
          format: date-time
          nullable: true
        home_formation:
          type: string
          nullable: true
          example: 4-3-3
        away_formation:
          type: string
          nullable: true
        referee:
          type: string
          nullable: true
        venue:
          type: string
          nullable: true
        attendance:
          type: integer
          nullable: true
        updated_at:
          type: string
          format: date-time
        home_logo:
          type: string
          format: uri
          nullable: true
        away_logo:
          type: string
          format: uri
          nullable: true
    MatchEvent:
      type: object
      required:
        - type
        - team
        - minute
      properties:
        id:
          type: integer
        match_id:
          type: string
        type:
          type: string
          description: Event type.
          enum:
            - goal
            - own-goal
            - penalty
            - missed-penalty
            - yellow-card
            - second-yellow
            - red-card
            - substitution
        team:
          type: integer
          description: 1 = home, 2 = away.
          enum:
            - 1
            - 2
        minute:
          type: integer
        added_time:
          type: integer
          nullable: true
          description: Stoppage-time minute (e.g. 45+2 → 2). Omitted when 0.
        player:
          type: string
          description: Player primarily associated with the event.
        player_in:
          type: string
          description: For substitutions, the incoming player. Otherwise omitted.
    LineupPlayer:
      type: object
      required:
        - team
        - is_starter
        - name
      properties:
        team:
          type: integer
          enum:
            - 1
            - 2
        is_starter:
          type: boolean
        player_id:
          type: string
        name:
          type: string
        jersey_no:
          type: integer
          nullable: true
        country:
          type: string
        position:
          type: integer
          nullable: true
          description: Numeric position code (1=GK, 2=DEF, 3=MID, 4=FWD).
    League:
      type: object
      required:
        - id
        - country
        - name
        - season
      properties:
        id:
          type: string
        country:
          type: string
          example: Spain
        name:
          type: string
          example: LaLiga
        season:
          type: string
          example: 2025/26
        logo_url:
          type: string
          format: uri
          nullable: true
    HealthStatus:
      type: object
      required:
        - status
        - components
        - timestamp
      properties:
        status:
          type: string
          enum:
            - ok
            - degraded
        components:
          type: object
          required:
            - postgres
            - redis
            - nats
          properties:
            postgres:
              $ref: '#/components/schemas/HealthComponent'
            redis:
              $ref: '#/components/schemas/HealthComponent'
            nats:
              $ref: '#/components/schemas/HealthComponent'
        timestamp:
          type: string
          format: date-time
    HealthComponent:
      type: object
      required:
        - ok
      properties:
        ok:
          type: boolean
        error:
          type: string
          description: Present only when `ok` is `false`.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable error message.
        code:
          type: string
          description: Machine-readable error code (when applicable).
        request_id:
          type: string
          description: Unique request identifier — quote when contacting support.
