{
  "openapi": "3.1.0",
  "info": {
    "title": "Cricstars Cricket APIs",
    "version": "1.1.0",
    "description": "Cricstars public read-only Academy and News APIs; cricket calculator APIs; provisional tournament planning. Calculator outputs are illustrative and may not implement all league-specific laws or competition rules. Public Academy and News endpoints use GET; calculators and tournament planning use POST. Shared IP-based rate limits apply."
  },
  "servers": [
    {
      "url": "https://cricstars.com"
    }
  ],
  "paths": {
    "/api/v1/tournament/plan": {
      "post": {
        "operationId": "createProvisionalTournamentPlan",
        "summary": "Generate a provisional tournament plan",
        "description": "Generate provisional fixtures using the supplied tournament format and capacity limits. Fixed per-client rate limits are 10 requests in 10 seconds and 30 requests in 60 seconds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanRequest"
              },
              "examples": {
                "eightTeamRoundRobin": {
                  "summary": "8 teams, single round robin",
                  "value": {
                    "format": "single",
                    "teams": 8,
                    "overs": 20,
                    "grounds": 2,
                    "slots": 2,
                    "days": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Provisional tournament plan; not a publish-ready fixture list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, input fields, or calculation parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only POST is accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "POST"
              }
            }
          },
          "413": {
            "description": "Request body exceeds 8192 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-client rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Policy": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Calculation validation or internal calculation failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Calculation gateway/upstream failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limiter unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/required-run-rate": {
      "post": {
        "operationId": "calculateRequiredRunRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate required run rate",
        "description": "Supply exactly one of oversUsed or ballsUsed. Current runs must be below target; legal deliveries must remain. JSON request body limit: 8192 bytes. Shared rate limiting: 10 requests/10s, 30 requests/60s per client IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcRequiredRunRateRequest"
              },
              "example": {
                "target": 201,
                "totalOvers": 20,
                "currentRuns": 110,
                "wicketsDown": 3,
                "oversUsed": "10.0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcRequiredRunRateResponse"
                },
                "example": {
                  "runsNeeded": 91,
                  "ballsRemaining": 60,
                  "oversRemaining": "10.0",
                  "requiredRunRate": "9.10",
                  "currentRunRate": "11.00",
                  "pressure": "High"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only POST is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 8192 bytes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Gateway or calculation service failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/net-run-rate": {
      "post": {
        "operationId": "calculateNetRunRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate net run rate",
        "description": "Simplified NRR only, without all-out adjustments. Supply both overs fields OR both balls fields; not both modes. JSON request body limit: 8192 bytes. Shared rate limiting: 10 requests/10s, 30 requests/60s per client IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcNetRunRateRequest"
              },
              "example": {
                "runsScored": 180,
                "runsConceded": 130,
                "oversFaced": "20.0",
                "oversBowled": "20.0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcNetRunRateResponse"
                },
                "example": {
                  "netRunRate": "2.50",
                  "scoringRate": "9.00",
                  "concededRate": "6.50",
                  "runsScored": 180,
                  "runsConceded": 130,
                  "scope": "Simplified innings/totals NRR; no all-out adjustment"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only POST is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 8192 bytes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Gateway or calculation service failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/overs-balls": {
      "post": {
        "operationId": "calculateOversBalls",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate overs and balls converter",
        "description": "Exactly one of overs or balls is required. Cricket notation 17.3 means 17 overs and 3 deliveries, not 17.3 decimal overs. JSON request body limit: 8192 bytes. Shared rate limiting: 10 requests/10s, 30 requests/60s per client IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcOversBallsRequest"
              },
              "example": {
                "overs": "17.3"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcOversBallsResponse"
                },
                "example": {
                  "overs": "17.3",
                  "balls": 105
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only POST is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 8192 bytes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Gateway or calculation service failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/follow-on": {
      "post": {
        "operationId": "calculateFollowOn",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate follow-on eligibility",
        "description": "Eligibility is advisory. The team leading may choose whether to enforce the follow-on. JSON request body limit: 8192 bytes. Shared rate limiting: 10 requests/10s, 30 requests/60s per client IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcFollowOnRequest"
              },
              "example": {
                "firstScore": 503,
                "secondScore": 290,
                "days": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcFollowOnResponse"
                },
                "example": {
                  "threshold": 200,
                  "lead": 213,
                  "eligible": true,
                  "shortBy": 0,
                  "note": "Threshold is based on scheduled match duration; eligibility alone does not enforce the follow-on."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only POST is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 8192 bytes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Gateway or calculation service failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/bowling-average": {
      "post": {
        "operationId": "calculateBowlingAverage",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate bowling average",
        "description": "Bowling average returns null if wickets equals zero. JSON request body limit: 8192 bytes. Shared rate limiting: 10 requests/10s, 30 requests/60s per client IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcBowlingAverageRequest"
              },
              "example": {
                "runsConceded": 100,
                "wickets": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcBowlingAverageResponse"
                },
                "example": {
                  "runsConceded": 100,
                  "wickets": 5,
                  "bowlingAverage": "20.00"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only POST is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request exceeds 8192 bytes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Gateway or calculation service failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/run-rate": {
      "post": {
        "operationId": "calculateRunRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate run rate",
        "description": "Exactly one of overs or balls must be provided; must represent positive balls. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcRunRateRequest"
              },
              "example": {
                "runs": 72,
                "overs": "12.0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcRunRateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/batting-average": {
      "post": {
        "operationId": "calculateBattingAverage",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate batting average",
        "description": "Dismissals = innings minus not outs. When dismissals are zero the battingAverage response is null. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcBattingAverageRequest"
              },
              "example": {
                "runs": 250,
                "innings": 8,
                "notOuts": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcBattingAverageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/strike-rate": {
      "post": {
        "operationId": "calculateStrikeRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate batting strike rate",
        "description": "Exactly one of overs or balls must be provided; this is a batting strike rate calculated per 100 deliveries. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcStrikeRateRequest"
              },
              "example": {
                "runs": 48,
                "balls": 35
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcStrikeRateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/bowling-strike-rate": {
      "post": {
        "operationId": "calculateBowlingStrikeRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate bowling strike rate",
        "description": "Requires positive overs and wickets. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcBowlingStrikeRateRequest"
              },
              "example": {
                "overs": "8.2",
                "wickets": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcBowlingStrikeRateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/economy-rate": {
      "post": {
        "operationId": "calculateEconomyRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate economy rate",
        "description": "Exactly one of overs or balls must be provided; positive delivery count required. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcEconomyRateRequest"
              },
              "example": {
                "runsConceded": 45,
                "overs": "8.0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcEconomyRateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/partnership": {
      "post": {
        "operationId": "calculatePartnership",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate batting partnership contribution",
        "description": "Optional legalBalls overrides the sum of batter balls only when greater than zero. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcPartnershipRequest"
              },
              "example": {
                "aRuns": 35,
                "aBalls": 30,
                "bRuns": 24,
                "bBalls": 25,
                "extras": 4
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcPartnershipResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/projected-score": {
      "post": {
        "operationId": "calculateProjectedScore",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Project innings score",
        "description": "oversCompleted must be positive and less than totalOvers; recentRuns and recentOvers must be supplied together. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcProjectedScoreRequest"
              },
              "example": {
                "currentRuns": 100,
                "oversCompleted": "16.0",
                "totalOvers": 20,
                "wicketsLost": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcProjectedScoreResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/over-rate": {
      "post": {
        "operationId": "calculateOverRate",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Calculate bowling over rate",
        "description": "targetOvers and totalMinutes are optional, used together for a projected finish rate. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcOverRateRequest"
              },
              "example": {
                "oversBowled": "20.0",
                "minutesElapsed": 85,
                "requiredRate": 15
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcOverRateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/nrr-scenario": {
      "post": {
        "operationId": "calculateNrrScenario",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Project simplified NRR scenario",
        "description": "For mode bat requires batScore,batOvers,oppScoreBat,oppOversBat; for chase requires oppScoreChase,oppOversChase,chaseScore,chaseOvers. Simplified NRR without all-out adjustment. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcNrrScenarioRequest"
              },
              "example": {
                "mode": "bat",
                "forRuns": 300,
                "forOvers": "40.0",
                "againstRuns": 280,
                "againstOvers": "40.0",
                "rivalNrr": 0.25,
                "matchOvers": 20,
                "batScore": 180,
                "batOvers": "20.0",
                "oppScoreBat": 140,
                "oppOversBat": "20.0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcNrrScenarioResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/points-table": {
      "post": {
        "operationId": "calculatePointsTable",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Rank a cricket points table",
        "description": "Ranking is by points, net run rate, wins and team name. This does not verify team played-match totals. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcPointsTableRequest"
              },
              "example": {
                "teams": [
                  {
                    "team": "A",
                    "p": 2,
                    "w": 2,
                    "nrr": 1.2
                  },
                  {
                    "team": "B",
                    "p": 2,
                    "w": 1,
                    "nrr": 0.1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcPointsTableResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cricket/qualification": {
      "post": {
        "operationId": "calculateQualification",
        "tags": [
          "Cricket Calculators"
        ],
        "summary": "Evaluate projected qualification cut-off",
        "description": "Models a single rival points cut-off scenario, not guaranteed qualification; rivalWins and rivalNR cannot exceed remaining matches in total. All calculations are provisional where competition-specific rules apply. Cricket overs inputs use string notation. JSON body limit and shared client-IP rate limits are enforced by the existing calculator gateway.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalcQualificationRequest"
              },
              "example": {
                "yourPoints": 8,
                "yourRemaining": 2,
                "yourNrr": 0.5,
                "rivalPoints": 10,
                "rivalRemaining": 2,
                "rivalNrr": 0.2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalcQualificationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content type must be application/json",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Shared rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream calculator failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/academy/lessons": {
      "get": {
        "operationId": "listAcademyLessons",
        "tags": [
          "Academy"
        ],
        "summary": "List Academy lessons and landing pages",
        "description": "Returns 18 lesson records and 2 Academy landing pages in the current catalogue; filters by category and search text.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 999999
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 60
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcademyListing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/academy/drills": {
      "get": {
        "operationId": "listAcademyDrills",
        "tags": [
          "Academy"
        ],
        "summary": "List cricket coaching drills",
        "description": "Returns public coaching drill catalogue entries, with pagination, query and category filters.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 999999
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 60
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcademyListing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/academy/search": {
      "get": {
        "operationId": "searchAcademy",
        "tags": [
          "Academy"
        ],
        "summary": "Search Academy and coaching catalogue",
        "description": "Searches slug, title, goal and category; q is optional, empty q lists all published catalogue records.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 999999
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 60
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcademyListing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/academy/content": {
      "get": {
        "operationId": "getAcademyContent",
        "tags": [
          "Academy"
        ],
        "summary": "Get individual Academy content",
        "description": "Look up a record by catalogue slug.",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcademyContent"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Content not found or unpublished",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news/latest": {
      "get": {
        "operationId": "listLatestNews",
        "tags": [
          "News"
        ],
        "summary": "Latest published Cricstars articles",
        "description": "Newest published articles, ordered by COALESCE(published_at,created_at) descending; default 10, maximum 50.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewsListing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news/articles": {
      "get": {
        "operationId": "listNewsArticles",
        "tags": [
          "News"
        ],
        "summary": "Paginated Cricstars articles",
        "description": "Lists published articles; optional exact category match. Default limit 20 and offset 0.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10000
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewsListing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news/article": {
      "get": {
        "operationId": "getNewsArticle",
        "tags": [
          "News"
        ],
        "summary": "Retrieve a published News article",
        "description": "Only records with published status are returned; raw stored article content may contain HTML or Markdown.",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 220
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewsArticle"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Content not found or unpublished",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news/categories": {
      "get": {
        "operationId": "listNewsCategories",
        "tags": [
          "News"
        ],
        "summary": "Published News categories",
        "description": "Categories derived only from published records; returns their record counts.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewsCategories"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news/search": {
      "get": {
        "operationId": "searchNews",
        "tags": [
          "News"
        ],
        "summary": "Search published News articles",
        "description": "Case-insensitive database LIKE matching on title, excerpt or category_name; q required with 2–200 characters.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public JSON response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewsListing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Client-IP rate limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Catalogue or news data unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PlanRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "format": {
            "type": "string",
            "enum": [
              "single",
              "double",
              "knockout",
              "groups"
            ],
            "description": "Tournament format."
          },
          "teams": {
            "type": "integer",
            "minimum": 2,
            "maximum": 64,
            "description": "Number of teams."
          },
          "overs": {
            "type": "integer",
            "minimum": 1,
            "maximum": 90,
            "description": "Overs per match."
          },
          "grounds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 32,
            "description": "Available grounds."
          },
          "slots": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "Match slots per ground per playing day."
          },
          "days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "Requested playing days."
          },
          "groups": {
            "type": "integer",
            "minimum": 2,
            "maximum": 16,
            "description": "Number of groups, permitted only for groups format."
          },
          "qualify": {
            "type": "integer",
            "minimum": 1,
            "maximum": 8,
            "description": "Qualifiers per group, permitted only for groups format."
          }
        },
        "required": [
          "format",
          "teams",
          "overs",
          "grounds",
          "slots",
          "days"
        ],
        "oneOf": [
          {
            "properties": {
              "format": {
                "const": "single"
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": [
                    "groups"
                  ]
                },
                {
                  "required": [
                    "qualify"
                  ]
                }
              ]
            }
          },
          {
            "properties": {
              "format": {
                "const": "double"
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": [
                    "groups"
                  ]
                },
                {
                  "required": [
                    "qualify"
                  ]
                }
              ]
            }
          },
          {
            "properties": {
              "format": {
                "const": "knockout"
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": [
                    "groups"
                  ]
                },
                {
                  "required": [
                    "qualify"
                  ]
                }
              ]
            }
          },
          {
            "properties": {
              "format": {
                "const": "groups"
              }
            },
            "required": [
              "groups",
              "qualify"
            ],
            "description": "groups must not exceed teams; qualify must not exceed floor(teams/groups)."
          }
        ]
      },
      "PlanResponse": {
        "type": "object",
        "required": [
          "status",
          "format",
          "teams",
          "totalMatches",
          "requiredPlayingDays",
          "requestedPlayingDays",
          "fitsRequestedDays",
          "fixtures",
          "warnings"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "provisional"
          },
          "format": {
            "type": "string",
            "enum": [
              "single",
              "double",
              "knockout",
              "groups"
            ]
          },
          "teams": {
            "type": "integer"
          },
          "totalMatches": {
            "type": "integer",
            "minimum": 0
          },
          "requiredPlayingDays": {
            "type": "integer",
            "minimum": 0
          },
          "requestedPlayingDays": {
            "type": "integer"
          },
          "fitsRequestedDays": {
            "type": "boolean"
          },
          "fixtures": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Provisional fixture records. Individual field names are not yet documented as stable."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "CricketOvers": {
        "type": "string",
        "pattern": "^\\d+(?:\\.[0-5])?$",
        "description": "Cricket overs notation; 17.3 = 17 overs and 3 legal balls. Must be a JSON string. The numeric range is additionally validated by the service.",
        "examples": [
          "17.3",
          "20.0"
        ]
      },
      "CalcRequiredRunRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "target": {
            "type": "integer",
            "minimum": 1
          },
          "totalOvers": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000
          },
          "currentRuns": {
            "type": "integer",
            "minimum": 0
          },
          "wicketsDown": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "oversUsed": {
            "$ref": "#/components/schemas/CricketOvers"
          },
          "ballsUsed": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "target",
          "totalOvers",
          "currentRuns",
          "wicketsDown"
        ],
        "oneOf": [
          {
            "required": [
              "oversUsed"
            ],
            "not": {
              "required": [
                "ballsUsed"
              ]
            }
          },
          {
            "required": [
              "ballsUsed"
            ],
            "not": {
              "required": [
                "oversUsed"
              ]
            }
          }
        ]
      },
      "CalcRequiredRunRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runsNeeded": {
            "type": "integer"
          },
          "ballsRemaining": {
            "type": "integer"
          },
          "oversRemaining": {
            "type": "string"
          },
          "requiredRunRate": {
            "type": "string"
          },
          "currentRunRate": {
            "type": "string"
          },
          "pressure": {
            "type": "string"
          }
        },
        "required": [
          "runsNeeded",
          "ballsRemaining",
          "oversRemaining",
          "requiredRunRate",
          "currentRunRate",
          "pressure"
        ]
      },
      "CalcNetRunRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runsScored": {
            "type": "integer",
            "minimum": 0
          },
          "runsConceded": {
            "type": "integer",
            "minimum": 0
          },
          "oversFaced": {
            "$ref": "#/components/schemas/CricketOvers"
          },
          "oversBowled": {
            "$ref": "#/components/schemas/CricketOvers"
          },
          "ballsFaced": {
            "type": "integer",
            "minimum": 1
          },
          "ballsBowled": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "runsScored",
          "runsConceded"
        ],
        "oneOf": [
          {
            "required": [
              "oversFaced",
              "oversBowled"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "ballsFaced"
                  ]
                },
                {
                  "required": [
                    "ballsBowled"
                  ]
                }
              ]
            }
          },
          {
            "required": [
              "ballsFaced",
              "ballsBowled"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "oversFaced"
                  ]
                },
                {
                  "required": [
                    "oversBowled"
                  ]
                }
              ]
            }
          }
        ]
      },
      "CalcNetRunRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "netRunRate": {
            "type": "string"
          },
          "scoringRate": {
            "type": "string"
          },
          "concededRate": {
            "type": "string"
          },
          "runsScored": {
            "type": "integer"
          },
          "runsConceded": {
            "type": "integer"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "netRunRate",
          "scoringRate",
          "concededRate",
          "runsScored",
          "runsConceded",
          "scope"
        ]
      },
      "CalcOversBallsRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "overs": {
            "$ref": "#/components/schemas/CricketOvers"
          },
          "balls": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [],
        "oneOf": [
          {
            "required": [
              "overs"
            ],
            "not": {
              "required": [
                "balls"
              ]
            }
          },
          {
            "required": [
              "balls"
            ],
            "not": {
              "required": [
                "overs"
              ]
            }
          }
        ]
      },
      "CalcOversBallsResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "overs": {
            "type": "string"
          },
          "balls": {
            "type": "integer"
          }
        },
        "required": [
          "overs",
          "balls"
        ]
      },
      "CalcFollowOnRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "firstScore": {
            "type": "integer",
            "minimum": 0
          },
          "secondScore": {
            "type": "integer",
            "minimum": 0
          },
          "days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          }
        },
        "required": [
          "firstScore",
          "secondScore",
          "days"
        ]
      },
      "CalcFollowOnResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "threshold": {
            "type": "integer"
          },
          "lead": {
            "type": "integer"
          },
          "eligible": {
            "type": "boolean"
          },
          "shortBy": {
            "type": "integer"
          },
          "note": {
            "type": "string"
          }
        },
        "required": [
          "threshold",
          "lead",
          "eligible",
          "shortBy",
          "note"
        ]
      },
      "CalcBowlingAverageRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runsConceded": {
            "type": "integer",
            "minimum": 0
          },
          "wickets": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "runsConceded",
          "wickets"
        ]
      },
      "CalcBowlingAverageResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runsConceded": {
            "type": "integer"
          },
          "wickets": {
            "type": "integer"
          },
          "bowlingAverage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Decimal result to two places; null if wickets is zero."
          }
        },
        "required": [
          "runsConceded",
          "wickets",
          "bowlingAverage"
        ]
      },
      "CalcRunRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runs": {
            "type": "integer",
            "minimum": 0
          },
          "overs": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "balls": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "runs"
        ],
        "description": "Exactly one of overs or balls must be provided; must represent positive balls."
      },
      "CalcRunRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runs": {
            "type": "integer"
          },
          "balls": {
            "type": "integer"
          },
          "runRate": {
            "type": "string"
          }
        },
        "required": [
          "runs",
          "balls",
          "runRate"
        ]
      },
      "CalcBattingAverageRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runs": {
            "type": "integer",
            "minimum": 0
          },
          "innings": {
            "type": "integer",
            "minimum": 1
          },
          "notOuts": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "runs",
          "innings"
        ],
        "description": "Dismissals = innings minus not outs. When dismissals are zero the battingAverage response is null."
      },
      "CalcBattingAverageResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runs": {
            "type": "integer"
          },
          "innings": {
            "type": "integer"
          },
          "notOuts": {
            "type": "integer"
          },
          "dismissals": {
            "type": "integer"
          },
          "battingAverage": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "runs",
          "innings",
          "notOuts",
          "dismissals",
          "battingAverage"
        ]
      },
      "CalcStrikeRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runs": {
            "type": "integer",
            "minimum": 0
          },
          "overs": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "balls": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "runs"
        ],
        "description": "Exactly one of overs or balls must be provided; this is a batting strike rate calculated per 100 deliveries."
      },
      "CalcStrikeRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runs": {
            "type": "integer"
          },
          "balls": {
            "type": "integer"
          },
          "strikeRate": {
            "type": "string"
          },
          "runsPerBall": {
            "type": "string"
          },
          "tempo": {
            "type": "string",
            "enum": [
              "Slow",
              "Steady",
              "Good",
              "Aggressive",
              "Explosive"
            ]
          }
        },
        "required": [
          "runs",
          "balls",
          "strikeRate",
          "runsPerBall",
          "tempo"
        ]
      },
      "CalcBowlingStrikeRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "overs": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "wickets": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "overs",
          "wickets"
        ],
        "description": "Requires positive overs and wickets."
      },
      "CalcBowlingStrikeRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "balls": {
            "type": "integer"
          },
          "wickets": {
            "type": "integer"
          },
          "bowlingStrikeRate": {
            "type": "string"
          }
        },
        "required": [
          "balls",
          "wickets",
          "bowlingStrikeRate"
        ]
      },
      "CalcEconomyRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runsConceded": {
            "type": "integer",
            "minimum": 0
          },
          "overs": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "balls": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "runsConceded"
        ],
        "description": "Exactly one of overs or balls must be provided; positive delivery count required."
      },
      "CalcEconomyRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "runsConceded": {
            "type": "integer"
          },
          "balls": {
            "type": "integer"
          },
          "economyRate": {
            "type": "string"
          },
          "runsPerBall": {
            "type": "string"
          },
          "control": {
            "type": "string",
            "enum": [
              "Elite",
              "Tight",
              "Decent",
              "Expensive",
              "Costly"
            ]
          }
        },
        "required": [
          "runsConceded",
          "balls",
          "economyRate",
          "runsPerBall",
          "control"
        ]
      },
      "CalcPartnershipRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "aRuns": {
            "type": "integer",
            "minimum": 0
          },
          "aBalls": {
            "type": "integer",
            "minimum": 0
          },
          "bRuns": {
            "type": "integer",
            "minimum": 0
          },
          "bBalls": {
            "type": "integer",
            "minimum": 0
          },
          "extras": {
            "type": "integer",
            "minimum": 0
          },
          "legalBalls": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "aRuns",
          "aBalls",
          "bRuns",
          "bBalls"
        ],
        "description": "Optional legalBalls overrides the sum of batter balls only when greater than zero."
      },
      "CalcPartnershipResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "partnershipRuns": {
            "type": "integer"
          },
          "legalBalls": {
            "type": "integer"
          },
          "overs": {
            "type": "string"
          },
          "runRate": {
            "type": "string"
          },
          "shareA": {
            "type": "string"
          },
          "shareB": {
            "type": "string"
          },
          "shareExtras": {
            "type": "string"
          },
          "aStrikeRate": {
            "type": "string"
          },
          "bStrikeRate": {
            "type": "string"
          },
          "leader": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "equal"
            ]
          }
        },
        "required": [
          "partnershipRuns",
          "legalBalls",
          "overs",
          "runRate",
          "shareA",
          "shareB",
          "shareExtras",
          "aStrikeRate",
          "bStrikeRate",
          "leader"
        ]
      },
      "CalcProjectedScoreRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currentRuns": {
            "type": "number",
            "minimum": 0
          },
          "oversCompleted": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "totalOvers": {
            "type": "number",
            "minimum": 0.01,
            "maximum": 1000
          },
          "wicketsLost": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "recentRuns": {
            "type": "number",
            "minimum": 0
          },
          "recentOvers": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          }
        },
        "required": [
          "currentRuns",
          "oversCompleted",
          "totalOvers"
        ],
        "description": "oversCompleted must be positive and less than totalOvers; recentRuns and recentOvers must be supplied together."
      },
      "CalcProjectedScoreResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currentRunRate": {
            "type": "string"
          },
          "projectedScore": {
            "type": "integer"
          },
          "baseScore": {
            "type": "integer"
          },
          "lowScore": {
            "type": "integer"
          },
          "highScore": {
            "type": "integer"
          },
          "ballsRemaining": {
            "type": "integer"
          },
          "wicketsLost": {
            "type": "integer"
          },
          "note": {
            "type": "string"
          },
          "recentRate": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "currentRunRate",
          "projectedScore",
          "baseScore",
          "lowScore",
          "highScore",
          "ballsRemaining",
          "wicketsLost",
          "note",
          "recentRate"
        ]
      },
      "CalcOverRateRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "oversBowled": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "minutesElapsed": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "requiredRate": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "targetOvers": {
            "type": "number",
            "minimum": 0
          },
          "totalMinutes": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "oversBowled",
          "minutesElapsed",
          "requiredRate"
        ],
        "description": "targetOvers and totalMinutes are optional, used together for a projected finish rate."
      },
      "CalcOverRateResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currentRate": {
            "type": "string"
          },
          "requiredRate": {
            "type": "string"
          },
          "difference": {
            "type": "string"
          },
          "finishRate": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "On track",
              "Behind"
            ]
          }
        },
        "required": [
          "currentRate",
          "requiredRate",
          "difference",
          "finishRate",
          "status"
        ]
      },
      "CalcNrrScenarioRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "bat",
              "chase"
            ]
          },
          "forRuns": {
            "type": "number",
            "minimum": 0
          },
          "forOvers": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "againstRuns": {
            "type": "number",
            "minimum": 0
          },
          "againstOvers": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "rivalNrr": {
            "type": "number"
          },
          "matchOvers": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000
          },
          "batScore": {
            "type": "integer",
            "minimum": 0
          },
          "batOvers": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "oppScoreBat": {
            "type": "number",
            "minimum": 0
          },
          "oppOversBat": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "oppScoreChase": {
            "type": "number",
            "minimum": 0
          },
          "oppOversChase": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          },
          "chaseScore": {
            "type": "number",
            "minimum": 0
          },
          "chaseOvers": {
            "type": "string",
            "pattern": "^\\d+(?:\\.[0-5])?$",
            "description": "Cricket notation as a string: 17.3 means 17 overs and 3 balls."
          }
        },
        "required": [
          "mode",
          "forRuns",
          "forOvers",
          "againstRuns",
          "againstOvers",
          "rivalNrr",
          "matchOvers"
        ],
        "description": "For mode bat requires batScore,batOvers,oppScoreBat,oppOversBat; for chase requires oppScoreChase,oppOversChase,chaseScore,chaseOvers. Simplified NRR without all-out adjustment."
      },
      "CalcNrrScenarioResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "bat",
              "chase"
            ]
          },
          "projectedNrr": {
            "type": "string"
          },
          "rivalNrr": {
            "type": "string"
          },
          "gap": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "Overtakes",
              "Short"
            ]
          },
          "required": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "mode",
          "projectedNrr",
          "rivalNrr",
          "gap",
          "status",
          "required",
          "scope"
        ]
      },
      "PointsTableTeamInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "team": {
            "type": "string",
            "maxLength": 120
          },
          "p": {
            "type": "integer",
            "minimum": 0
          },
          "w": {
            "type": "integer",
            "minimum": 0
          },
          "l": {
            "type": "integer",
            "minimum": 0
          },
          "t": {
            "type": "integer",
            "minimum": 0
          },
          "nr": {
            "type": "integer",
            "minimum": 0
          },
          "bonus": {
            "type": "number",
            "minimum": 0
          },
          "penalty": {
            "type": "number",
            "minimum": 0
          },
          "nrr": {
            "type": "number"
          }
        },
        "required": [
          "team"
        ]
      },
      "CalcPointsTableRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "teams": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/PointsTableTeamInput"
            }
          },
          "winPts": {
            "type": "number",
            "minimum": 0
          },
          "tiePts": {
            "type": "number",
            "minimum": 0
          },
          "nrPts": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "teams"
        ],
        "description": "Ranking is by points, net run rate, wins and team name. This does not verify team played-match totals."
      },
      "CalcPointsTableResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "teams": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PointsTableTeamResult"
            }
          },
          "leader": {
            "type": "string"
          },
          "teamCount": {
            "type": "integer"
          },
          "rankingRules": {
            "type": "string"
          }
        },
        "required": [
          "teams",
          "leader",
          "teamCount",
          "rankingRules"
        ]
      },
      "CalcQualificationRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "yourPoints": {
            "type": "number",
            "minimum": 0
          },
          "yourRemaining": {
            "type": "integer",
            "minimum": 0
          },
          "yourNrr": {
            "type": "number"
          },
          "rivalPoints": {
            "type": "number",
            "minimum": 0
          },
          "rivalRemaining": {
            "type": "integer",
            "minimum": 0
          },
          "rivalWins": {
            "type": "integer",
            "minimum": 0
          },
          "rivalNR": {
            "type": "integer",
            "minimum": 0
          },
          "rivalNrr": {
            "type": "number"
          },
          "winPts": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "nrPts": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "yourPoints",
          "yourRemaining",
          "yourNrr",
          "rivalPoints",
          "rivalRemaining",
          "rivalNrr"
        ],
        "description": "Models a single rival points cut-off scenario, not guaranteed qualification; rivalWins and rivalNR cannot exceed remaining matches in total."
      },
      "CalcQualificationResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string"
          },
          "tieWins": {
            "type": [
              "integer",
              "null"
            ]
          },
          "safeWins": {
            "type": [
              "integer",
              "null"
            ]
          },
          "nrrGap": {
            "type": "string"
          },
          "cutoff": {
            "type": "string"
          },
          "maximumPoints": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "status",
          "tieWins",
          "safeWins",
          "nrrGap",
          "cutoff",
          "maximumPoints",
          "scope"
        ]
      },
      "PointsTableTeamResult": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "rank": {
            "type": "integer",
            "minimum": 1
          },
          "team": {
            "type": "string",
            "maxLength": 120
          },
          "p": {
            "type": "integer",
            "minimum": 0
          },
          "w": {
            "type": "integer",
            "minimum": 0
          },
          "l": {
            "type": "integer",
            "minimum": 0
          },
          "t": {
            "type": "integer",
            "minimum": 0
          },
          "nr": {
            "type": "integer",
            "minimum": 0
          },
          "bonus": {
            "type": "number",
            "minimum": 0
          },
          "penalty": {
            "type": "number",
            "minimum": 0
          },
          "nrr": {
            "type": "number"
          },
          "pts": {
            "type": "number"
          },
          "pct": {
            "type": "string"
          }
        },
        "required": [
          "rank",
          "team",
          "p",
          "w",
          "l",
          "t",
          "nr",
          "bonus",
          "penalty",
          "nrr",
          "pts",
          "pct"
        ]
      },
      "AcademyItem": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "type",
          "category"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "academy-lesson",
              "academy-page",
              "coaching-drill"
            ]
          },
          "category": {
            "type": "string"
          },
          "goal": {
            "type": "string"
          }
        },
        "additionalProperties": true,
        "description": "Public catalogue record with type-specific descriptive fields; the catalogue is published as static JSON."
      },
      "AcademyListing": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AcademyItem"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "items"
        ]
      },
      "AcademyContent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "item": {
            "$ref": "#/components/schemas/AcademyItem"
          }
        },
        "required": [
          "item"
        ]
      },
      "NewsItem": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "excerpt": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "coverImage": {
            "type": "string"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "title",
          "slug",
          "url",
          "excerpt",
          "category",
          "tags",
          "coverImage",
          "publishedAt",
          "updatedAt"
        ]
      },
      "NewsFullItem": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "excerpt": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "coverImage": {
            "type": "string"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "content": {
            "type": "string"
          }
        },
        "required": [
          "title",
          "slug",
          "url",
          "excerpt",
          "category",
          "tags",
          "coverImage",
          "publishedAt",
          "updatedAt",
          "content"
        ]
      },
      "NewsListing": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NewsItem"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "items"
        ]
      },
      "NewsArticle": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "item": {
            "$ref": "#/components/schemas/NewsFullItem"
          }
        },
        "required": [
          "item"
        ]
      },
      "NewsCategories": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "name",
                "total"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "total": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "required": [
          "items"
        ]
      }
    }
  },
  "externalDocs": {
    "description": "API usage and limitations",
    "url": "https://cricstars.com/api-docs/tournament-planner/"
  },
  "tags": [
    {
      "name": "Cricket Calculators",
      "description": "Cricket calculation endpoints accepting JSON POST bodies."
    },
    {
      "name": "Academy",
      "description": "Read-only Academy lessons, pages and drills."
    },
    {
      "name": "News",
      "description": "Published Cricstars blog articles only."
    }
  ]
}
