Metadata-Version: 2.4
Name: fc-clubs-api
Version: 0.1.1
Summary: Unofficial Python client for the EA Sports FC 27 Pro Clubs API, returning pandas DataFrames.
Author: Erkan Dogan
License-Expression: MIT
Project-URL: Homepage, https://github.com/1erkandogan/fc27-clubs-api
Project-URL: Issues, https://github.com/1erkandogan/fc27-clubs-api/issues
Keywords: ea,fc27,pro clubs,api,pandas
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: pandas>=1.5
Dynamic: license-file

# FC 27 Clubs API

An unofficial Python client for the **EA Sports FC 27 Pro Clubs API**, the same
endpoints `proclubs.ea.com` calls from the browser. You can find clubs and get
their stats, members, matches and per-player match ratings as tables (pandas
DataFrames).

Not affiliated with or endorsed by EA. The endpoints are undocumented and can
change without notice.

- **New to Python, or want to know how it works?** Read [docs/how-it-works.md](https://github.com/1erkandogan/fc27-clubs-api/blob/main/docs/how-it-works.md).
  It walks through the code and explains every import and decision.
- **Want EA's raw responses?** See [docs/endpoints.md](https://github.com/1erkandogan/fc27-clubs-api/blob/main/docs/endpoints.md).

## Install

You need Python 3.9 or newer.

```bash
pip install fc-clubs-api   # also installs pandas, the only dependency
```

No API key needed. The whole client is one file, [`fc_clubs_api.py`](https://github.com/1erkandogan/fc27-clubs-api/blob/main/fc_clubs_api.py),
and you import it as `fc_clubs_api`.

To work on the code itself, install from a clone instead:

```bash
git clone https://github.com/1erkandogan/fc27-clubs-api.git
cd fc27-clubs-api
pip install -e .   # your edits to fc_clubs_api.py take effect without reinstalling
```

## Quick start

```python
from fc_clubs_api import FC27API

api = FC27API(timezone="Europe/Istanbul")      # match times in your timezone

club_id = api.find_club_id("Your Club Name")   # -> the id, e.g. 1001

print(api.get_club_matches(club_id))           # last 10 league matches
print(api.get_member_stats(club_id))           # squad stats this season
```

Or run the example: `python examples/quickstart.py "Your Club Name"`

## Methods

| Method | Gives you (one row per ...) |
|---|---|
| `find_club_id(name)` | the club's id as a number. If several clubs match, the error lists them. |
| `search_club_by_name(name)` | club matching the name: division, wins, goals, points |
| `get_club_details(club_id)` | club (1 row): name, ids, stadium |
| `get_club_overall_stats(club_id)` | club (1 row): record, goals, streaks, skill rating |
| `get_member_stats(club_id)` | member: games, goals, assists, average rating, pass/tackle rates |
| `get_member_career_stats(club_id)` | member: career totals at the club |
| `get_club_matches(club_id, match_type="leagueMatch", count=10)` | match, from your side: opponent, score, `result` (win/draw/loss), `dnf` |
| `get_match_players(club_id, match_type="leagueMatch", count=10, both_teams=False)` | player per match: rating, goals, assists, shots, passes, tackles |
| `get_playoff_achievements(club_id)` | achievement (EA has only returned an empty list so far) |
| `get_json(endpoint, params)` | EA's raw response as dicts/lists, e.g. `api.get_json("clubs/info", {"clubIds": 1001})` |

- `match_type` is `"leagueMatch"`, `"friendlyMatch"` or `"playoffMatch"`.
- By default, tables have a short set of readable columns. Add `all_columns=True` to
  `search_club_by_name`, `get_club_details`, `get_club_overall_stats`,
  `get_member_stats` or `get_match_players` to get every field EA sends, with EA's names.
- `FC27API(platform="common-gen5", timeout=10, timezone="UTC")`: all settings are optional.
- If there is no data, you get an empty table (`df.empty` is `True`).

## Recipes

```python
from fc_clubs_api import FC27API

api = FC27API(timezone="Europe/Istanbul")
club_id = api.find_club_id("Your Club Name")

# Top 5 scorers this season
members = api.get_member_stats(club_id)
print(members.sort_values("goals", ascending=False).head(5)[["name", "goals", "assists"]])

# Last 5 league results
matches = api.get_club_matches(club_id)
print(matches.head(5)[["timestamp", "opponentName", "goals", "goalsAgainst", "result"]])

# Win rate over the last 10 league matches
wins = (matches["result"] == "win").sum()
print(f"Won {wins} of {len(matches)}")

# One player's recent matches
players = api.get_match_players(club_id)
print(players[players["name"] == "YourGamertag"][["timestamp", "rating", "goals", "assists"]])

# Average match rating per player, best first
print(players.groupby("name")["rating"].mean().sort_values(ascending=False))

# Save any table to open in Excel
members.to_csv("members.csv", index=False)
# members.to_excel("members.xlsx", index=False)   # needs: pip install openpyxl
```

## Errors

- **`FC27APIError`**: EA couldn't be reached, refused the request, or didn't send JSON.
- **`ValueError`**: you passed something invalid, such as an unknown `match_type`, or a
  club name that `find_club_id` couldn't match to one club.

```python
from fc_clubs_api import FC27API, FC27APIError

try:
    df = FC27API().get_club_details(1001)
except FC27APIError as error:
    print("EA problem:", error)
```

## Good to know

- **EA blocks non-browser requests.** Every request sends browser-like headers
  (`HEADERS` in `fc_clubs_api.py`). Without them EA answers 403 or doesn't answer at all.
  Plain `curl` is blocked even with the headers, while Python works.
- **No caching or rate limiting.** Every call goes straight to EA. If you loop over
  many clubs, add a pause between requests (`time.sleep(1)`) so you don't get blocked.

## Tests

```bash
python -m unittest discover -s tests -v
```

The tests run offline, using EA responses saved in `tests/fixtures/`. They keep the real structure,
but every club and player name, id and date is a placeholder.

## Contributing

Bug reports are welcome, especially when EA changes something and the client
breaks. Please open an issue before writing code: the project is deliberately
small and some things that look like oversights are decisions. See
[CONTRIBUTING.md](https://github.com/1erkandogan/fc27-clubs-api/blob/main/CONTRIBUTING.md).

## License

MIT, see [LICENSE.md](https://github.com/1erkandogan/fc27-clubs-api/blob/main/LICENSE.md).
