openapi: 3.1.0
info:
  title: InfieldHQ API
  version: 0.1.0-beta
  summary: Read-only beta of the InfieldHQ HTTP API.
  description: |
    The InfieldHQ API lets you read your league's own data: your account and
    roles, seasons, divisions, teams, rosters, and per-player playing time.

    This is a public beta. It is read-only, and the shapes below are subject to
    change while the beta runs. Every endpoint here is live today against the
    same production backend the app uses.

    ## Authentication

    During the beta, requests authenticate with a bearer token tied to your
    InfieldHQ account:

        Authorization: Bearer <your-token>

    A token only reaches data your account can already see in the app. Email
    hello@infieldhq.com to join the beta and we will help you get a token.
    Self-serve API keys are on the roadmap, not shipped yet.

    ## Errors

    Errors come back in one of two shapes, and clients should handle both:

    - Business, auth, or 5xx errors return a plain string:
      `{ "error": "Only league admins or team coaches can edit the lineup" }`
    - Validation failures (400) return a flattened error object:
      `{ "error": { "formErrors": [], "fieldErrors": { "teamId": ["..."] } } }`

    ## Limits

    No published rate limits during the beta. Be reasonable. There is no CORS
    allowance for third-party browser origins yet, so call the API from a server
    or the command line, not a web page.
servers:
  - url: https://api.infieldhq.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Account
  - name: Structure
  - name: Roster
  - name: Playing time
paths:
  /api/me:
    get:
      tags: [Account]
      operationId: getMe
      summary: Your account, leagues, and roles
      description: >
        Returns the signed-in account: display name, every league you belong to
        with your role (admin or coach), and the leagues where you are a parent.
        A good first call to confirm your token works.
      responses:
        "200":
          description: The current account.
          content:
            application/json:
              schema:
                type: object
                required: [userId, profile, leagues, parentLeagueIds]
                properties:
                  userId: { type: string, format: uuid }
                  profile:
                    type: object
                    properties:
                      displayName: { type: string }
                  leagues:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, format: uuid }
                        name: { type: string }
                        role: { type: string, enum: [admin, coach] }
                        aiAssistantEnabled: { type: boolean }
                        onboardingMode:
                          type: [string, "null"]
                          enum: [eval, null]
                  parentLeagueIds:
                    type: array
                    items: { type: string, format: uuid }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/seasons:
    get:
      tags: [Structure]
      operationId: listSeasons
      summary: Seasons in your leagues
      responses:
        "200":
          description: Non-archived seasons across the leagues you belong to.
          content:
            application/json:
              schema:
                type: object
                properties:
                  seasons:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, format: uuid }
                        leagueId: { type: string, format: uuid }
                        name: { type: string }
                        startsAt: { type: [string, "null"], format: date }
                        endsAt: { type: [string, "null"], format: date }
                        isActive: { type: boolean }
                        defaultInnings: { type: [integer, "null"] }
                        rotationPolicy: { type: [string, "null"] }
                        createdAt: { type: string, format: date-time }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/divisions:
    get:
      tags: [Structure]
      operationId: listDivisions
      summary: Divisions in your leagues
      responses:
        "200":
          description: Divisions across the leagues you belong to.
          content:
            application/json:
              schema:
                type: object
                properties:
                  divisions:
                    type: array
                    items: { type: object }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/teams:
    get:
      tags: [Structure]
      operationId: listTeams
      summary: Teams you can see
      parameters:
        - in: query
          name: season_id
          required: false
          schema: { type: string, format: uuid }
          description: Narrow the list to one season.
      responses:
        "200":
          description: Teams in your leagues, with roster counts and division context.
          content:
            application/json:
              schema:
                type: object
                properties:
                  teams:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, format: uuid }
                        leagueId: { type: string, format: uuid }
                        name: { type: string, description: "funName, or officialName when no fun name is set" }
                        officialName: { type: string }
                        funName: { type: [string, "null"] }
                        color: { type: [string, "null"] }
                        seasonId: { type: [string, "null"], format: uuid }
                        divisionId: { type: [string, "null"], format: uuid }
                        divisionName: { type: [string, "null"] }
                        minTeamSize: { type: [integer, "null"] }
                        maxTeamSize: { type: [integer, "null"] }
                        playerCount: { type: integer }
                        defaultPositionCount: { type: integer, description: "9 or 10" }
                        createdAt: { type: string, format: date-time }
                        updatedAt: { type: string, format: date-time }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/teams/{teamId}/players:
    get:
      tags: [Roster]
      operationId: listTeamPlayers
      summary: A team's roster
      parameters:
        - in: path
          name: teamId
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: >
            Players on the team. Includes coarse eligibility booleans (canPitch,
            canCatch) derived from registration answers. Raw registration text is
            never returned.
          content:
            application/json:
              schema:
                type: object
                properties:
                  players:
                    type: array
                    items: { type: object }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/seasons/{seasonId}/growth-report:
    get:
      tags: [Playing time]
      operationId: getGrowthReport
      summary: Per-player playing time for a season
      description: >
        A read-only, per-player rollup of where each player has played across a
        season: position frequency, infield vs outfield split, bench load, and
        batting-slot spread. The data behind "are we rotating fairly?"
      parameters:
        - in: path
          name: seasonId
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: teamId
          required: false
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: The season growth report.
          content:
            application/json:
              schema: { type: object }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: A bearer token tied to your InfieldHQ account. Email hello@infieldhq.com to join the beta.
  schemas:
    ErrorString:
      type: object
      properties:
        error: { type: string }
    ErrorValidation:
      type: object
      properties:
        error:
          type: object
          properties:
            formErrors: { type: array, items: { type: string } }
            fieldErrors: { type: object, additionalProperties: { type: array, items: { type: string } } }
  responses:
    BadRequest:
      description: Missing or invalid parameters.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/ErrorString"
              - $ref: "#/components/schemas/ErrorValidation"
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorString" }
    Forbidden:
      description: Your account cannot see this resource.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorString" }
    NotFound:
      description: The resource does not exist or is not visible to you.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorString" }
