Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@
"edge/en/tools/search-research/serpapi-googlesearchtool",
"edge/en/tools/search-research/serpapi-googleshoppingtool",
"edge/en/tools/search-research/databricks-query-tool",
"edge/en/tools/search-research/youai-search"
"edge/en/tools/search-research/youai-search",
"edge/en/tools/search-research/livetennistool"
]
},
{
Expand Down
69 changes: 69 additions & 0 deletions docs/edge/en/tools/search-research/livetennistool.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: Live Tennis Tool
description: Live tennis scores, fixtures, player profiles and rankings for CrewAI agents via the Live Tennis API.
icon: trophy
mode: "wide"
---

# `LiveTennisTool`

## Description

Query professional tennis data from the [Live Tennis API](https://livetennisapi.com): matches currently in play with live scores, upcoming matches, scheduled fixtures, player search and profiles (including current ranking), published ranking tables, and your API quota usage.

The tool is scoped to REST endpoints, most of which are available on the API's free tier (keyed, 30 requests/minute, 100 requests/day), so it can be tried without payment. Completed-match history is a paid feature of the API and is not part of this tool.

## Environment Variables

```bash
LIVETENNIS_API_KEY=your_api_key # Free key at livetennisapi.com
```

## Actions

| `action` | What it returns | Plan |
|---|---|---|
| `live_matches` | Matches in play with current scores (optional `tour` filter) | Free |
| `upcoming_matches` | Matches starting soon | Free |
| `fixtures` | Scheduled matches | Free |
| `search_players` | Player search by name (requires `search`) | Free |
| `player_profile` | Single player detail, including current ranking (requires `player_id`) | Free |
| `rankings` | Published ranking table (requires `system`: `atp`, `wta`, `itf_jt`, `itf_mt`, `itf_wt`, `utr`) | PRO |
| `usage` | Your API quota vs. consumption | Free |

## Basic Usage

```python
from crewai import Agent
from crewai_tools import LiveTennisTool

tool = LiveTennisTool()

reporter = Agent(
role="Tennis Reporter",
goal="Summarise what is happening on tour right now",
backstory="You follow professional tennis and report live developments.",
tools=[tool],
)
```

## Direct Invocation

```python
from crewai_tools import LiveTennisTool

tool = LiveTennisTool()

# Matches in play right now
print(tool.run(action="live_matches", tour="atp"))

# Find a player, then load their profile
players = tool.run(action="search_players", search="alcaraz")
profile = tool.run(action="player_profile", player_id="<id from search>")
```

## Error Handling

The tool returns readable messages (rather than raising) when the API key is missing, invalid (401), the endpoint needs a higher plan (403), or the rate limit is hit (429), so an agent can recover or report the problem.

Full API documentation: [docs.livetennisapi.com](https://docs.livetennisapi.com).
2 changes: 2 additions & 0 deletions lib/crewai-tools/src/crewai_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
)
from crewai_tools.tools.json_search_tool.json_search_tool import JSONSearchTool
from crewai_tools.tools.linkup.linkup_search_tool import LinkupSearchTool
from crewai_tools.tools.live_tennis_tool.live_tennis_tool import LiveTennisTool
from crewai_tools.tools.llamaindex_tool.llamaindex_tool import LlamaIndexTool
from crewai_tools.tools.mdx_search_tool.mdx_search_tool import MDXSearchTool
from crewai_tools.tools.merge_agent_handler_tool.merge_agent_handler_tool import (
Expand Down Expand Up @@ -280,6 +281,7 @@
"JSONSearchTool",
"JinaScrapeWebsiteTool",
"LinkupSearchTool",
"LiveTennisTool",
"LlamaIndexTool",
"MCPServerAdapter",
"MDXSearchTool",
Expand Down
2 changes: 2 additions & 0 deletions lib/crewai-tools/src/crewai_tools/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
)
from crewai_tools.tools.json_search_tool.json_search_tool import JSONSearchTool
from crewai_tools.tools.linkup.linkup_search_tool import LinkupSearchTool
from crewai_tools.tools.live_tennis_tool.live_tennis_tool import LiveTennisTool
from crewai_tools.tools.llamaindex_tool.llamaindex_tool import LlamaIndexTool
from crewai_tools.tools.mdx_search_tool.mdx_search_tool import MDXSearchTool
from crewai_tools.tools.merge_agent_handler_tool.merge_agent_handler_tool import (
Expand Down Expand Up @@ -264,6 +265,7 @@
"JSONSearchTool",
"JinaScrapeWebsiteTool",
"LinkupSearchTool",
"LiveTennisTool",
"LlamaIndexTool",
"MDXSearchTool",
"MergeAgentHandlerTool",
Expand Down
60 changes: 60 additions & 0 deletions lib/crewai-tools/src/crewai_tools/tools/live_tennis_tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Live Tennis Tool

Query professional tennis data from the [Live Tennis API](https://livetennisapi.com) — live match scores, upcoming matches, scheduled fixtures, player search and profiles, ranking tables, and API usage.

The tool is scoped to REST endpoints, most of which are available on the free tier, so it can be tried without payment.

## Actions

| `action` | Endpoint | Plan | Notes |
|---|---|---|---|
| `live_matches` | `GET /matches?status=live` | Free | Matches in play with current scores |
| `upcoming_matches` | `GET /matches?status=upcoming` | Free | Matches starting soon |
| `fixtures` | `GET /fixtures` | Free | Scheduled matches |
| `search_players` | `GET /players?search=` | Free | Requires `search` |
| `player_profile` | `GET /players/{id}` | Free | Requires `player_id`; includes current ranking |
| `rankings` | `GET /rankings?system=` | PRO | Requires `system` (`atp`, `wta`, `itf_jt`, `itf_mt`, `itf_wt`, `utr`) |
| `usage` | `GET /usage` | Free | Your quota vs. consumption; exempt from quota |
Comment thread
coderabbitai[bot] marked this conversation as resolved.

The free tier is keyed and rate-limited to 30 requests/minute and 100 requests/day. Completed-match history is a paid feature and is not part of this tool. The API also offers a WebSocket push feed and model win probability on its top tier; this tool intentionally sticks to the polling REST surface.

## Environment Variables

```env
LIVETENNIS_API_KEY=your_api_key
```

Get a free key at [livetennisapi.com](https://livetennisapi.com). Full API documentation: [docs.livetennisapi.com](https://docs.livetennisapi.com).

## Example Usage

```python
from crewai_tools import LiveTennisTool

tool = LiveTennisTool()

# Matches in play right now
print(tool.run(action="live_matches", tour="atp"))

# Find a player, then load their profile
players = tool.run(action="search_players", search="alcaraz")
profile = tool.run(action="player_profile", player_id="<id from search>")
```

## With an Agent

```python
from crewai import Agent
from crewai_tools import LiveTennisTool

reporter = Agent(
role="Tennis Reporter",
goal="Summarise what is happening on tour right now",
backstory="You follow professional tennis and report live developments.",
tools=[LiveTennisTool()],
)
```

## Error Handling

The tool returns readable messages (rather than raising) when the API key is missing, invalid (401), the endpoint needs a higher plan (403), or the rate limit is hit (429), so an agent can recover or report the problem.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from crewai_tools.tools.live_tennis_tool.live_tennis_tool import LiveTennisTool
from crewai_tools.tools.live_tennis_tool.schemas import LiveTennisToolSchema


__all__ = [
"LiveTennisTool",
"LiveTennisToolSchema",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Tool for querying professional tennis data from the Live Tennis API."""

from __future__ import annotations

import json
import os
from typing import Any
from urllib.parse import quote

from crewai.tools import BaseTool, EnvVar
from pydantic import BaseModel, Field, ValidationError
import requests

from crewai_tools.tools.live_tennis_tool.schemas import LiveTennisToolSchema


API_KEY_ENV_VAR = "LIVETENNIS_API_KEY"


class LiveTennisTool(BaseTool):
"""Query the Live Tennis API (https://livetennisapi.com).

Covers the endpoints available on the free tier: live match scores,
upcoming matches, scheduled fixtures, player search and profiles
(including current ranking), and API usage. The 'rankings' action
(full published ranking tables) requires a paid plan.
"""

name: str = "Live Tennis API"
description: str = (
"Fetches professional tennis data from the Live Tennis API: matches "
"currently in play with live scores, upcoming matches, scheduled "
"fixtures, player search and profiles (including current ranking and "
"ranking points), published ranking tables, and your API quota usage. "
"Results are returned as JSON."
)
args_schema: type[BaseModel] = LiveTennisToolSchema
base_url: str = "https://api.livetennisapi.com/api/public/v1"
request_timeout: float = 30.0
env_vars: list[EnvVar] = Field(
default_factory=lambda: [
EnvVar(
name=API_KEY_ENV_VAR,
description="API key for the Live Tennis API (free tier available)",
required=True,
),
]
)

def _run(self, **kwargs: Any) -> str:
api_key = os.environ.get(API_KEY_ENV_VAR, "").strip()
if not api_key:
return (
f"The {API_KEY_ENV_VAR} environment variable is not set. "
"Get a free API key at https://livetennisapi.com and set "
f"{API_KEY_ENV_VAR} to use this tool."
)

try:
params = LiveTennisToolSchema(**kwargs)
except ValidationError as e:
return f"Invalid arguments for the Live Tennis API tool: {e}"

path, query = self._build_request(params)
try:
response = requests.get(
f"{self.base_url}{path}",
headers={"Authorization": f"Bearer {api_key}"},
params=query,
timeout=self.request_timeout,
)
except requests.RequestException as e:
return f"Live Tennis API request failed: {e}"

if response.status_code == 401:
return (
"Live Tennis API rejected the request (401): the "
f"{API_KEY_ENV_VAR} key is missing, unknown, or disabled."
)
if response.status_code == 403:
return (
"Live Tennis API returned 403: this endpoint requires a higher "
f"subscription tier than the current key provides. {response.text}"
)
if response.status_code == 429:
return f"Live Tennis API rate limit exceeded (429): {response.text}"
if not response.ok:
return (
f"Live Tennis API returned HTTP {response.status_code}: {response.text}"
)

try:
return json.dumps(response.json())
except ValueError:
return response.text

@staticmethod
def _build_request(params: LiveTennisToolSchema) -> tuple[str, dict[str, Any]]:
"""Map a validated action to its endpoint path and query parameters."""
query: dict[str, Any] = {}
if params.limit is not None:
query["limit"] = params.limit
if params.offset is not None:
query["offset"] = params.offset

if params.action in ("live_matches", "upcoming_matches"):
query["status"] = "live" if params.action == "live_matches" else "upcoming"
if params.tour:
query["tour"] = params.tour
return "/matches", query
if params.action == "fixtures":
if params.tour:
query["tour"] = params.tour
return "/fixtures", query
if params.action == "search_players":
query["search"] = params.search
return "/players", query
if params.action == "player_profile":
return f"/players/{quote(str(params.player_id), safe='')}", {}
if params.action == "rankings":
query["system"] = params.system
return "/rankings", query
return "/usage", {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Pydantic input schemas for the Live Tennis API tool."""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field, model_validator


LiveTennisAction = Literal[
"live_matches",
"upcoming_matches",
"fixtures",
"search_players",
"player_profile",
"rankings",
"usage",
]

RankingSystem = Literal["atp", "wta", "itf_jt", "itf_mt", "itf_wt", "utr"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated


class LiveTennisToolSchema(BaseModel):
"""Input schema for LiveTennisTool."""

action: LiveTennisAction = Field(
...,
description=(
"Operation to perform: 'live_matches' (matches in play right now, "
"with current scores), 'upcoming_matches' (matches starting soon), "
"'fixtures' (scheduled matches), 'search_players' (find players by "
"name, requires 'search'), 'player_profile' (single player detail "
"including current ranking, requires 'player_id'), 'rankings' "
"(published ranking table, requires 'system'), 'usage' (your API "
"quota and consumption)."
),
)
tour: str | None = Field(
default=None,
description=(
"Optional tour filter for match and fixture actions, e.g. 'atp' or 'wta'."
),
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
search: str | None = Field(
default=None,
description=(
"Player name (or name fragment) to look up. Required for the "
"'search_players' action."
),
)
player_id: str | None = Field(
default=None,
description=(
"Player id as returned by 'search_players'. Required for the "
"'player_profile' action."
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
)
system: RankingSystem | None = Field(
default=None,
description=(
"Ranking system for the 'rankings' action: 'atp', 'wta', 'itf_jt', "
"'itf_mt', 'itf_wt' or 'utr'."
),
)
limit: int | None = Field(
default=None,
ge=1,
description="Maximum number of results to return (API default: 50).",
)
offset: int | None = Field(
default=None,
ge=0,
description="Pagination offset into the result list.",
)

@model_validator(mode="after")
def _validate_action_arguments(self) -> LiveTennisToolSchema:
if self.action == "search_players" and not (
self.search and self.search.strip()
):
raise ValueError("'search' is required for the 'search_players' action.")
if self.action == "player_profile" and not (
self.player_id and self.player_id.strip()
):
raise ValueError("'player_id' is required for the 'player_profile' action.")
if self.action == "rankings" and self.system is None:
raise ValueError("'system' is required for the 'rankings' action.")
return self
Loading