openapi: 3.1.0
info:
  title: Zafronix World Cup API
  version: 1.2.0
  summary: Historical FIFA World Cup data (1930-2026) plus 2026 live state.
  description: "Read endpoints under /fifa/worldcup/v1/*. Authenticated via X-API-Key. See https://api.zafronix.com/docs for prose docs."
  contact:
    name: Zafronix Support
    url: "https://zafronix.com/contact?subject=worldcup-api"
  license:
    name: Commercial — see /pricing
    url: "https://api.zafronix.com/pricing"
servers:
  - 
      url: "https://api.zafronix.com/fifa/worldcup/v1"
      description: Production
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: "Read keys: zwc_pk_*, zwc_free_*. Write keys: zwc_sk_*. Sandbox: zwc_skt_*."
  schemas:
    Error:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          example: not_found
        message:
          type: string
        request_id:
          type: string
        detail:
          type: object
    Tournament:
      type: object
      additionalProperties: true
      description: "Full tournament document. See /docs#tournaments."
    Match:
      type: object
      additionalProperties: true
      description: "Match record. See /docs#matches."
    Standing:
      type: object
      additionalProperties: true
      description: "Computed group-table row. See /docs#standings."
    BracketSlot:
      type: object
      additionalProperties: true
      description: KO bracket slot with placeholder→team resolution.
    HistoryEvent:
      type: object
      additionalProperties: true
      description: Audit log entry for a write operation.
    Flag:
      type: object
      properties:
        iso:
          type:
            - string
            - "null"
        iso3166_3:
          type:
            - string
            - "null"
        fifaCode:
          type:
            - string
            - "null"
        flagUrl:
          type: string
        flagAttribution:
          type: string
  parameters:
    Year:
      name: year
      in: query
      required: true
      schema:
        type: integer
        minimum: 1930
        maximum: 9999
    OptionalYear:
      name: year
      in: query
      required: false
      schema:
        type: integer
        minimum: 1930
        maximum: 9999
    MatchId:
      name: matchId
      in: path
      required: true
      schema:
        type: string
        pattern: ^\d{4}-\d{3}$
    Cursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
      description: Opaque pagination cursor from a previous response.
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 500
    TeamName:
      name: name
      in: path
      required: true
      schema:
        type: string
      description: Team name. Hyphens treated as spaces, case-insensitive.
security:
  - 
      ApiKeyAuth: []
tags:
  - 
      name: Meta
  - 
      name: Tournaments
  - 
      name: Teams
  - 
      name: Matches
  - 
      name: Match Writes
  - 
      name: Computed
  - 
      name: Players
  - 
      name: Stadiums
  - 
      name: Referees
  - 
      name: Trivia
  - 
      name: Other
  - 
      name: Aggregates
  - 
      name: Discovery
  - 
      name: Sandbox
paths:
  "/":
    get:
      summary: API info
      security: []
      tags:
        - Meta
      responses:
        "200":
          description: OK
  "/health":
    get:
      summary: Health probe
      security: []
      tags:
        - Meta
      responses:
        "200":
          description: OK
        "503":
          description: Dataset failed to load
  "/tournaments":
    get:
      summary: List all tournaments
      security: []
      tags:
        - Tournaments
      responses:
        "200":
          description: OK
          headers:
            Cache-Control:
              schema:
                type: string
              description: Tier-specific freshness directive.
            ETag:
              schema:
                type: string
              description: SHA-256 prefix of the response body. Send back as If-None-Match for 304.
        "304":
          description: Not Modified
  "/tournaments/{year}":
    get:
      summary: Tournament document with teams + flag metadata
      tags:
        - Tournaments
      parameters:
        - 
                  name: year
                  in: path
                  required: true
                  schema:
                    type: integer
                    minimum: 1930
                    maximum: 9999
      responses:
        "200":
          description: OK
          headers:
            Cache-Control:
              schema:
                type: string
              description: Tier-specific freshness directive.
            ETag:
              schema:
                type: string
              description: SHA-256 prefix of the response body. Send back as If-None-Match for 304.
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Daily quota for the caller key.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests left in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Epoch seconds when the window resets.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Tournament"
        "304":
          description: Not Modified
        "401":
          description: Unauthorized
        "404":
          description: Year not found
  "/teams":
    get:
      summary: Teams in a tournament or cross-tournament lookup by country
      tags:
        - Teams
      description: Provide exactly one of `?tournament=YYYY` (returns all teams in that year) or `?country=<name>` (returns cross-tournament appearances for that country, equivalent to `GET /teams/{name}`). Hyphens in country names are treated as spaces.
      parameters:
        - 
                  name: tournament
                  in: query
                  required: false
                  schema:
                    type: integer
                    minimum: 1930
                    maximum: 9999
                  description: 4-digit tournament year. Mutually exclusive with country.
        - 
                  name: country
                  in: query
                  required: false
                  schema:
                    type: string
                  description: "Country name. Hyphens treated as spaces. Mutually exclusive with tournament. Example: New-Zealand"
      responses:
        "200":
          description: OK
          headers:
            Cache-Control:
              schema:
                type: string
              description: Tier-specific freshness directive.
            ETag:
              schema:
                type: string
              description: SHA-256 prefix of the response body. Send back as If-None-Match for 304.
        "400":
          description: Neither or both params supplied
        "404":
          description: Tournament year or country not found
  "/teams/{name}":
    get:
      summary: Cross-tournament appearances + flag
      tags:
        - Teams
      parameters:
        - 
                  name: name
                  in: path
                  required: true
                  schema:
                    type: string
      responses:
        "200":
          description: OK
        "404":
          description: Team not found
  "/teams/{name}/roster":
    get:
      summary: Squad for a single tournament
      tags:
        - Teams
      parameters:
        - 
                  name: name
                  in: path
                  required: true
                  schema:
                    type: string
        - 
                  "$ref": "#/components/parameters/Year"
      responses:
        "200":
          description: OK
        "404":
          description: Team or year not found
  "/teams/{name}/rivalries":
    get:
      summary: Top rivalries for a specific team
      tags:
        - Teams
      parameters:
        - 
                  "$ref": "#/components/parameters/TeamName"
        - 
                  name: limit
                  in: query
                  required: false
                  schema:
                    type: integer
                    minimum: 1
                    maximum: 50
                    default: 5
                  description: Max rivalries to return.
      responses:
        "200":
          description: OK
        "404":
          description: Team not found or no rivalry data
  "/referees":
    get:
      summary: List referees across all World Cups
      tags:
        - Referees
      parameters:
        - 
                  name: q
                  in: query
                  required: false
                  schema:
                    type: string
                  description: Substring search on referee name.
        - 
                  name: country
                  in: query
                  required: false
                  schema:
                    type: string
                  description: Filter by country (case-insensitive exact match).
        - 
                  name: year
                  in: query
                  required: false
                  schema:
                    type: integer
                    minimum: 1930
                    maximum: 9999
                  description: Filter to referees who officiated in this year.
        - 
                  "$ref": "#/components/parameters/Limit"
        - 
                  name: offset
                  in: query
                  required: false
                  schema:
                    type: integer
                    minimum: 0
      responses:
        "200":
          description: OK
  "/referees/{id}":
    get:
      summary: Single referee with full match list
      tags:
        - Referees
      parameters:
        - 
                  name: id
                  in: path
                  required: true
                  schema:
                    type: string
                  description: Slug of the referee name (lowercase, accents stripped, hyphens).
      responses:
        "200":
          description: OK
        "404":
          description: Referee not found
  "/on-this-day":
    get:
      summary: World Cup matches and facts for a given calendar date
      tags:
        - Trivia
      parameters:
        - 
                  name: date
                  in: query
                  required: false
                  schema:
                    type: string
                    pattern: ^(\d{4}-)?\d{2}-\d{2}$
                  description: MM-DD (anniversary across all years) or YYYY-MM-DD (single date). Defaults to today (UTC).
      responses:
        "200":
          description: OK
        "400":
          description: Invalid date format
  "/matches":
    get:
      summary: List matches. With ?year — single tournament. Without ?year — cursor-paginated cross-tournament.
      tags:
        - Matches
      parameters:
        - 
                  "$ref": "#/components/parameters/OptionalYear"
        - 
                  name: stage
                  in: query
                  schema:
                    type: string
                  description: Accepts both legacy (qf, r32) and canonical (quarter_final, round_of_32).
        - 
                  name: team
                  in: query
                  schema:
                    type: string
        - 
                  name: stadiumId
                  in: query
                  schema:
                    type: string
        - 
                  name: order
                  in: query
                  schema:
                    type: string
                    enum:
                      - asc
                      - desc
        - 
                  "$ref": "#/components/parameters/Cursor"
        - 
                  "$ref": "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
  "/matches/{matchId}":
    get:
      summary: Single match
      tags:
        - Matches
      parameters:
        - 
                  "$ref": "#/components/parameters/MatchId"
      responses:
        "200":
          description: OK
        "404":
          description: Match not found
    patch:
      summary: Correct fields on a finalized match
      tags:
        - Match Writes
      parameters:
        - 
                  "$ref": "#/components/parameters/MatchId"
      requestBody:
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: OK
        "404":
          description: Match not found
        "422":
          description: Validation error
  "/matches/{matchId}/history":
    get:
      summary: Audit log for a match
      tags:
        - Matches
      parameters:
        - 
                  "$ref": "#/components/parameters/MatchId"
      responses:
        "200":
          description: OK
        "404":
          description: Match not found
  "/matches/{matchId}/result":
    post:
      summary: Finalize a scoreline
      tags:
        - Match Writes
      parameters:
        - 
                  "$ref": "#/components/parameters/MatchId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - homeScore
                - awayScore
              properties:
                homeScore:
                  type: integer
                  minimum: 0
                awayScore:
                  type: integer
                  minimum: 0
                extraTime:
                  type: boolean
                penalties:
                  type: object
                  properties:
                    home:
                      type: integer
                    away:
                      type: integer
                attendance:
                  type: integer
                referee:
                  type: object
                  properties:
                    name:
                      type: string
                    country:
                      type: string
                finalizedAt:
                  type: string
                  format: date-time
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Match"
        "403":
          description: Read key on write endpoint
        "404":
          description: Match not found
        "409":
          description: Already finalized — use PATCH for corrections
        "422":
          description: Validation error
  "/matches/{matchId}/postpone":
    post:
      summary: Mark postponed/abandoned/cancelled
      tags:
        - Match Writes
      parameters:
        - 
                  "$ref": "#/components/parameters/MatchId"
      responses:
        "200":
          description: OK
        "422":
          description: Validation error
  "/standings":
    get:
      summary: Group standings with FIFA tiebreakers
      tags:
        - Computed
      parameters:
        - 
                  "$ref": "#/components/parameters/Year"
        - 
                  name: group
                  in: query
                  schema:
                    type: string
                    pattern: ^[A-Za-z]$
      responses:
        "200":
          description: OK
  "/bracket":
    get:
      summary: KO bracket with placeholder resolution
      tags:
        - Computed
      parameters:
        - 
                  "$ref": "#/components/parameters/Year"
      responses:
        "200":
          description: OK
  "/players":
    get:
      summary: Search/paginate players
      tags:
        - Players
      parameters:
        - 
                  name: q
                  in: query
                  schema:
                    type: string
                    minLength: 2
                    maxLength: 80
        - 
                  "$ref": "#/components/parameters/Cursor"
        - 
                  "$ref": "#/components/parameters/Limit"
      responses:
        "200":
          description: OK
  "/players/{name}":
    get:
      summary: Cross-tournament player career
      tags:
        - Players
      parameters:
        - 
                  name: name
                  in: path
                  required: true
                  schema:
                    type: string
      responses:
        "200":
          description: OK
        "404":
          description: Not found
  "/stadiums":
    get:
      summary: List stadiums
      tags:
        - Stadiums
      responses:
        "200":
          description: OK
  "/stadiums/{id}":
    get:
      summary: Stadium by id
      tags:
        - Stadiums
      parameters:
        - 
                  name: id
                  in: path
                  required: true
                  schema:
                    type: string
      responses:
        "200":
          description: OK
        "404":
          description: Not found
  "/trivia":
    get:
      summary: Trivia facts
      tags:
        - Other
      parameters:
        - 
                  name: year
                  in: query
                  schema:
                    type: integer
        - 
                  name: category
                  in: query
                  schema:
                    type: string
      responses:
        "200":
          description: OK
  "/compare":
    get:
      summary: Compare years
      tags:
        - Other
      parameters:
        - 
                  name: years
                  in: query
                  schema:
                    type: string
                  description: Comma-separated YYYY,YYYY
      responses:
        "200":
          description: OK
  "/aggregates/champions":
    get:
      summary: Title counts by country
      tags:
        - Aggregates
      responses:
        "200":
          description: OK
  "/aggregates/players":
    get:
      summary: Position + birth-month + hemisphere splits
      tags:
        - Aggregates
      parameters:
        - 
                  name: years
                  in: query
                  schema:
                    type: string
      responses:
        "200":
          description: OK
  "/search":
    get:
      summary: Cross-entity search (BM25/TF-IDF, accent-folded)
      tags:
        - Discovery
      parameters:
        - 
                  name: q
                  in: query
                  required: true
                  schema:
                    type: string
                    minLength: 2
                    maxLength: 120
        - 
                  name: types
                  in: query
                  schema:
                    type: string
                  description: "Comma-separated: team, player, match, stadium, tournament"
        - 
                  "$ref": "#/components/parameters/OptionalYear"
        - 
                  name: limit
                  in: query
                  schema:
                    type: integer
                    minimum: 1
                    maximum: 100
      responses:
        "200":
          description: OK
        "400":
          description: Invalid types
  "/me/usage":
    get:
      summary: "Caller usage stats: quota, latency, top endpoints"
      tags:
        - Discovery
      responses:
        "200":
          description: OK
          headers:
            Cache-Control:
              schema:
                type: string
                example: private, no-cache
  "/sandbox/status":
    get:
      summary: Sandbox state + counters
      tags:
        - Sandbox
      responses:
        "200":
          description: OK
        "403":
          description: Sandbox key required
  "/sandbox/reset":
    post:
      summary: Reset year=9999 fixtures
      tags:
        - Sandbox
      responses:
        "200":
          description: OK
        "403":
          description: Sandbox key required
        "429":
          description: Rate limited (10/hour/key)
