Async & sync Python wrapper for the Clash of Clans API with auto key management
Project description
cocapi
A Python wrapper for the official Clash of Clans API with full async support, automatic key management, caching, retries, and optional Pydantic models.
Install
pip install cocapi
# With Pydantic model support
pip install 'cocapi[pydantic]'
Requires Python 3.10+.
Quick Start
With an API Token
Get a token from developer.clashofclans.com:
from cocapi import CocApi
api = CocApi("your_api_token")
clan = api.clan_tag("#2PP")
print(clan["name"])
player = api.players("#900PUCPV")
print(player["trophies"])
With Email/Password (Automatic Key Management)
Skip manual key creation entirely. cocapi logs into the developer portal, detects your IP, and manages keys for you:
from cocapi import CocApi
api = CocApi.from_credentials("you@example.com", "your_password")
clan = api.clan_tag("#2PP")
Keys are created automatically, reused when valid, and rotated when your IP changes. See Authentication for details.
Async
import asyncio
from cocapi import CocApi
async def main():
async with CocApi("your_token") as api:
clan = await api.clan_tag("#2PP")
player = await api.players("#900PUCPV")
print(clan["name"], player["trophies"])
asyncio.run(main())
Authentication
cocapi supports two ways to authenticate:
1. API Token
Pass a token directly. You manage key creation and IP binding yourself at the developer portal.
api = CocApi("your_token")
2. Developer Portal Credentials
Provide your email and password. cocapi handles everything:
api = CocApi.from_credentials("email", "password")
What happens automatically:
- Logs into the developer portal and detects your public IP
- Creates API keys bound to that IP (or reuses existing ones)
- If your IP changes mid-session and the API returns 403, revokes the old key and creates a new one
- Respects the 10-key-per-account SuperCell limit
Configuration Options
from cocapi import CocApi, ApiConfig
config = ApiConfig(
key_count=2, # Number of keys to maintain (default: 1)
key_name="my-bot", # Key name on the portal (default: "cocapi_auto")
auto_refresh_keys=True, # Auto-rotate on IP change (default: True)
)
api = CocApi.from_credentials("email", "password", config=config)
Persisting Keys Locally
For scripts that run repeatedly, enable persist_keys to cache the token on disk. On the next run, if your IP hasn't changed, the cached token is reused without contacting the developer portal:
config = ApiConfig(persist_keys=True)
api = CocApi.from_credentials("email", "password", config=config)
# First run: logs in, creates key, saves to ~/.cocapi/keys.json
# Next runs: detects IP, matches cache, skips login entirely
Disabled by default. Storage path is customizable via key_storage_path.
Standalone Key Manager
Use SyncKeyManager or AsyncKeyManager directly if you need tokens outside of CocApi:
from cocapi import SyncKeyManager
with SyncKeyManager("email", "password", key_count=3) as km:
tokens = km.manage_keys()
print(f"Got {len(tokens)} token(s)")
# Async version
from cocapi import AsyncKeyManager
async with AsyncKeyManager("email", "password") as km:
tokens = await km.manage_keys()
Configuration
All options are set through ApiConfig:
from cocapi import CocApi, ApiConfig
config = ApiConfig(
# Request settings
timeout=30,
max_retries=3,
retry_delay=1.0, # Base delay for exponential backoff
# Caching
enable_caching=True,
cache_ttl=600, # Seconds (default: 300)
# Rate limiting (async only)
enable_rate_limiting=True,
requests_per_second=10.0,
burst_limit=20,
# Metrics
enable_metrics=True,
metrics_window_size=1000,
# Pydantic models (requires cocapi[pydantic])
use_pydantic_models=True,
)
api = CocApi("your_token", config=config)
# Runtime management
api.get_cache_stats()
api.get_metrics()
api.clear_cache()
api.clear_metrics()
Middleware
Add custom processing to requests and responses:
from cocapi import CocApi
from cocapi.middleware import add_user_agent_middleware, add_request_id_middleware
api = CocApi("your_token")
api.add_request_middleware(add_user_agent_middleware("MyApp/1.0"))
api.add_request_middleware(add_request_id_middleware())
# Custom middleware
def add_custom_header(url, headers, params):
headers["X-My-Header"] = "value"
return url, headers, params
api.add_request_middleware(add_custom_header)
Custom Endpoints
Call new SuperCell endpoints without waiting for a library update:
result = api.custom_endpoint("/new-endpoint")
result = api.custom_endpoint("/clans/search", {"name": "clash", "limit": 10})
# With dynamic Pydantic model
result = api.custom_endpoint("/new-endpoint", use_dynamic_model=True)
Base URL Override
For proxies or testing environments:
config = ApiConfig(base_url="https://my-proxy.com/clash/v1")
api = CocApi("token", config=config)
# Or at runtime
api.set_base_url("https://staging.example.com/v1", force=True)
api.reset_base_url()
Pagination
Endpoints that return lists (clan members, rankings, leagues, etc.) use cursor-based pagination. The paginate() helper auto-follows cursors and yields items one by one:
# Iterate through all clan members
for member in api.paginate(api.clan_members, "#CLAN_TAG"):
print(member["name"])
# Custom page size
for clan in api.paginate(api.location_id_clan_rank, "32000087", limit=50):
print(clan["name"])
Pass the method reference and its arguments (excluding the params dict). The helper manages limit and after internally.
Works with async too:
async with CocApi("token") as api:
async for member in api.paginate(api.clan_members, "#CLAN_TAG"):
print(member["name"])
Works with any list endpoint: clan_members, clan_war_log, clan_capitalraidseasons, location, location_id_clan_rank, location_id_player_rank, league, league_season, league_season_id, labels_clans, labels_players, capitalleagues, builderbaseleagues, leaguetiers, and all other ranking/listing endpoints.
Note:
clan()search has a special signature and should be paginated manually using theparamsdict if needed.
Batch Fetch
Fetch multiple resources in one call. Sync runs sequentially; async runs concurrently with asyncio.gather:
# Fetch multiple players at once
results = api.batch(api.players, ["#TAG1", "#TAG2", "#TAG3"])
for player in results:
print(player["name"], player["trophies"])
# Methods with multiple args — pass tuples
results = api.batch(api.league_season_id, [("29000022", "2025-01"), ("29000022", "2025-02")])
Async with concurrency control:
async with CocApi("token") as api:
results = await api.batch(api.clan_tag, clan_tags, max_concurrent=5)
If one call fails, its position in the result list gets the error dict. Other calls are unaffected.
Event Polling
Monitor clans, wars, and players in real time with poll-and-compare. Async only.
import asyncio
from cocapi import CocApi, ApiConfig
from cocapi.events import EventStream, EventType
async def main():
async with CocApi("token", config=ApiConfig(enable_caching=False)) as api:
stream = EventStream(api)
stream.watch_clans(["#2PP"], interval=60)
stream.watch_wars(["#2PP"], interval=30)
stream.watch_players(["#900PUCPV"], interval=120)
async with stream:
async for event in stream:
print(event.event_type, event.tag)
for change in event.changes:
print(f" {change.field}: {change.old_value} -> {change.new_value}")
asyncio.run(main())
Event Types
| Event | Trigger |
|---|---|
CLAN_UPDATED |
Any clan field changed (level, points, description, etc.) |
MEMBER_JOINED |
New member detected in clan |
MEMBER_LEFT |
Member no longer in clan |
MEMBER_UPDATED |
Existing member's data changed (trophies, donations, etc.) |
MEMBER_ROLE_CHANGED |
Member promoted or demoted (member → elder → co-leader) |
MEMBER_DONATIONS |
Member donation count changed |
WAR_STATE_CHANGED |
War state transition (notInWar → preparation → inWar → warEnded) |
WAR_ATTACK_NEW |
New attack detected in an active or ended war |
PLAYER_UPDATED |
Any tracked player top-level field changed |
TROOP_UPGRADED |
Individual troop level increased |
SPELL_UPGRADED |
Individual spell level increased |
HERO_UPGRADED |
Individual hero level increased |
HERO_EQUIPMENT_UPGRADED |
Hero equipment level increased |
TOWNHALL_UPGRADED |
Town Hall level increased |
BUILDERHALL_UPGRADED |
Builder Hall level increased |
PLAYER_NAME_CHANGED |
Player name changed |
PLAYER_LEAGUE_CHANGED |
Player league changed |
PLAYER_LABEL_CHANGED |
Player labels added or removed |
MAINTENANCE_START |
API entered maintenance mode (HTTP 503) |
MAINTENANCE_END |
API recovered from maintenance |
POLL_ERROR |
API error during polling (watcher continues) |
Callbacks
Use decorators instead of (or alongside) the async generator:
stream = EventStream(api)
stream.watch_clans(["#2PP"])
@stream.on(EventType.MEMBER_JOINED)
async def on_join(event):
print(f"{event.metadata['member_name']} joined {event.tag}!")
@stream.on(EventType.WAR_STATE_CHANGED)
async def on_war(event):
print(f"War: {event.metadata['war_state_from']} -> {event.metadata['war_state_to']}")
await stream.run() # Blocks, dispatches to callbacks
Options
- Field filtering for player watchers:
watch_players(tags, include_fields=frozenset({"trophies"}))orexclude_fields - Member tracking:
watch_clans(tags, track_members=False)to skip join/leave/update detection - Backpressure:
EventStream(api, queue_size=1000)— bounded queue prevents unbounded memory growth - Maintenance detection:
stream.watch_maintenance(interval=30)— detects API 503 maintenance windows - State persistence:
EventStream(api, persist_path="state.json")— saves snapshots on stop, restores on start
Examples
Runnable scripts in the examples/ folder:
| Script | What it covers |
|---|---|
basic_usage.py |
Sync: clan info, player info, search, error handling |
async_usage.py |
Async with context manager, concurrent requests |
credential_auth.py |
Email/password login, key persistence, auto-refresh |
pagination_batch.py |
paginate() and batch() in sync and async |
event_polling.py |
EventStream with async for — real-time monitoring |
event_callbacks.py |
@stream.on() decorators and stream.run() |
configuration.py |
ApiConfig, caching, metrics, middleware |
# Run any example (replace the token inside first)
python examples/basic_usage.py
CLI
Install with the cli extra:
pip install 'cocapi[cli]'
Login with credentials (persisted)
Log in once with your developer portal email/password. The API key is saved to ~/.cocapi/keys.json and reused automatically:
cocapi login --email you@example.com --password yourpass
# Now all commands work without --token
cocapi clan "#2PP"
cocapi goldpass
Other authentication methods
# Explicit token
cocapi clan "#2PP" --token YOUR_TOKEN
# Environment variable
export COCAPI_TOKEN="YOUR_TOKEN"
cocapi clan "#2PP"
# Inline credentials (also persisted)
cocapi clan "#2PP" --email you@example.com --password yourpass
Commands
Add --json to any command for raw JSON output. Add --limit N where supported.
Clans
cocapi clan "#2PP" # Clan info
cocapi members "#2PP" --limit 10 # Clan members
cocapi war "#2PP" # Current war
cocapi warlog "#2PP" # War log
cocapi cwl "#2PP" # CWL league group
cocapi raids "#2PP" # Capital raid seasons
cocapi search "clash" --limit 5 # Search clans by name
cocapi search "war" --min-level 10 --war-frequency always # Advanced search
Players
cocapi player "#900PUCPV" # Player info
cocapi verify-token "#900PUCPV" "abc123" # Verify in-game token
Locations & Rankings
cocapi locations # List all locations
cocapi location 32000006 # Specific location info
cocapi rankings 32000087 clans # Clan rankings for location
cocapi rankings 32000087 players # Player rankings
cocapi rankings 32000087 clans-builder-base # Builder base clan rankings
cocapi rankings 32000087 players-builder-base
cocapi rankings 32000087 capitals # Capital rankings
Leagues
cocapi leagues # List leagues
cocapi league 29000022 # Specific league info
cocapi league-seasons 29000022 # List seasons (Legend League)
cocapi league-seasons 29000022 2025-01 # Season rankings
cocapi war-leagues # List war leagues
cocapi war-leagues 48000000 # Specific war league
cocapi capital-leagues # List capital leagues
cocapi capital-leagues 85000000 # Specific capital league
cocapi builder-base-leagues # List builder base leagues
cocapi builder-base-leagues 44000000 # Specific builder base league
cocapi league-tiers # List league tiers
cocapi league-tiers 105000001 # Specific league tier
Other
cocapi goldpass # Current gold pass season
cocapi labels clans # Clan labels
cocapi labels players # Player labels
cocapi cwl-war "#WARTAG" # Specific CWL war
API Reference
All methods work in both sync and async mode. In async, use await. Pagination parameters (limit, after, before) can be passed as a dict.
Clans
Clan Info
api.clan_tag(tag) # e.g. "#2PP"
Response
{
"tag": "string",
"name": "string",
"type": "string",
"description": "string",
"location": {"id": 0, "name": "string", "isCountry": true, "countryCode": "string"},
"isFamilyFriendly": true,
"badgeUrls": {"small": "string", "large": "string", "medium": "string"},
"clanLevel": 0,
"clanPoints": 0,
"clanBuilderBasePoints": 0,
"clanCapitalPoints": 0,
"capitalLeague": {"id": 0, "name": "string"},
"requiredTrophies": 0,
"requiredBuilderBaseTrophies": 0,
"requiredTownhallLevel": 0,
"warFrequency": "string",
"warWinStreak": 0,
"warWins": 0,
"isWarLogPublic": true,
"warLeague": {"id": 0, "name": "string"},
"members": 0,
"memberList": [{"tag": "string", "name": "string", "role": "string", "townHallLevel": 0, "expLevel": 0, "trophies": 0, "clanRank": 0, "donations": 0, "donationsReceived": 0}],
"labels": [{"id": 0, "name": "string", "iconUrls": {}}],
"clanCapital": {"capitalHallLevel": 0, "districts": []},
"chatLanguage": {"id": 0, "name": "string", "languageCode": "string"}
}
Clan Members
api.clan_members(tag)
api.clan_members(tag, {"limit": 20})
Search Clans
api.clan("clash", 10)
# With filters
api.clan(
"war", 20,
war_frequency="always",
location_id=32000006,
min_members=30,
max_members=50,
min_clan_points=20000,
min_clan_level=10,
label_ids="56000000,56000001",
)
War Log
api.clan_war_log(tag)
Response
{"items": [{"result": "string", "endTime": "string", "teamSize": 0, "attacksPerMember": 0, "clan": {"tag": "string", "name": "string", "stars": 0, "destructionPercentage": 0.0}, "opponent": {"tag": "string", "name": "string", "stars": 0, "destructionPercentage": 0.0}}], "paging": {"cursors": {}}}
Current War
api.clan_current_war(tag)
Response
{"state": "string", "teamSize": 0, "preparationStartTime": "string", "startTime": "string", "endTime": "string", "clan": {"tag": "string", "name": "string", "clanLevel": 0, "attacks": 0, "stars": 0, "destructionPercentage": 0.0, "members": [{"tag": "string", "name": "string", "townhallLevel": 0, "mapPosition": 0, "attacks": [{"order": 0, "attackerTag": "string", "defenderTag": "string", "stars": 0, "destructionPercentage": 0}]}]}, "opponent": {"tag": "string", "name": "string", "clanLevel": 0, "members": []}}
Clan War League Group
api.clan_leaguegroup(tag)
Response
{"tag": "string", "state": "string", "season": "string", "clans": [{"tag": "string", "clanLevel": 0, "name": "string", "members": [{"tag": "string", "townHallLevel": 0, "name": "string"}], "badgeUrls": {}}], "rounds": [{"warTags": ["string"]}]}
Capital Raid Seasons
api.clan_capitalraidseasons(tag)
api.clan_capitalraidseasons(tag, {"limit": 5})
CWL War by Tag
api.warleague(war_tag)
Players
Player Info
api.players(player_tag) # e.g. "#900PUCPV"
Response
{
"tag": "string",
"name": "string",
"townHallLevel": 0,
"expLevel": 0,
"trophies": 0,
"bestTrophies": 0,
"warStars": 0,
"attackWins": 0,
"defenseWins": 0,
"builderHallLevel": 0,
"builderBaseTrophies": 0,
"role": "string",
"warPreference": "string",
"donations": 0,
"donationsReceived": 0,
"clan": {"tag": "string", "name": "string", "clanLevel": 0, "badgeUrls": {}},
"achievements": [{"name": "string", "stars": 0, "value": 0, "target": 0, "info": "string"}],
"troops": [{"name": "string", "level": 0, "maxLevel": 0, "village": "string"}],
"heroes": [{"name": "string", "level": 0, "maxLevel": 0, "village": "string"}],
"heroEquipment": [],
"spells": [{"name": "string", "level": 0, "maxLevel": 0, "village": "string"}]
}
Verify Player Token
api.verify_player_token(player_tag, "player_api_token")
Returns {"tag": "string", "token": "string", "status": "string"}.
Locations
All Locations
api.location()
api.location({"limit": 10})
Response
{"items": [{"id": 0, "name": "string", "isCountry": true, "countryCode": "string"}], "paging": {"cursors": {}}}
Single Location
api.location_id("32000006")
Response
{"id": 0, "name": "string", "isCountry": true, "countryCode": "string"}
Top Clans in a Location
api.location_id_clan_rank(location_id)
api.location_id_clan_rank(location_id, {"limit": 10})
Response
{"items": [{"tag": "string", "name": "string", "location": {"id": 0, "name": "string", "isCountry": true, "countryCode": "string"}, "badgeUrls": {}, "clanLevel": 0, "members": 0, "clanPoints": 0, "rank": 0, "previousRank": 0}], "paging": {"cursors": {}}}
Top Players in a Location
api.location_id_player_rank(location_id)
Response
{"items": [{"tag": "string", "name": "string", "expLevel": 0, "trophies": 0, "attackWins": 0, "defenseWins": 0, "rank": 0, "previousRank": 0, "clan": {"tag": "string", "name": "string", "badgeUrls": {}}, "league": {"id": 0, "name": "string", "iconUrls": {}}, "leagueTier": {"id": 0, "name": "string", "iconUrls": {}}}], "paging": {"cursors": {}}}
Top Builder Base Clans in a Location
api.location_clans_builder_base(location_id)
Response
{"items": [{"tag": "string", "name": "string", "location": {"id": 0, "name": "string", "isCountry": true, "countryCode": "string"}, "badgeUrls": {}, "clanLevel": 0, "members": 0, "rank": 0, "previousRank": 0, "clanBuilderBasePoints": 0}], "paging": {"cursors": {}}}
Top Builder Base Players in a Location
api.location_players_builder_base(location_id)
Response
{"items": [{"tag": "string", "name": "string", "expLevel": 0, "rank": 0, "previousRank": 0, "builderBaseTrophies": 0, "clan": {"tag": "string", "name": "string", "badgeUrls": {}}, "builderBaseLeague": {"id": 0, "name": "string"}}], "paging": {"cursors": {}}}
Capital Rankings in a Location
api.location_capital_rankings(location_id)
Response
{"items": [{"tag": "string", "name": "string", "location": {"id": 0, "name": "string", "isCountry": true, "countryCode": "string"}, "badgeUrls": {}, "clanLevel": 0, "members": 0, "rank": 0, "previousRank": 0, "clanCapitalPoints": 0}], "paging": {"cursors": {}}}
Top Versus Clans in a Location (Deprecated)
api.location_clan_versus(location_id)
Deprecated: May return an error with
error_type: "deprecated".
Top Versus Players in a Location (Deprecated)
api.location_player_versus(location_id)
Deprecated: May return an error with
error_type: "deprecated".
Leagues
List Leagues
api.league()
api.league({"limit": 5})
Response
{"items": [{"id": 0, "name": "string", "iconUrls": {}}], "paging": {"cursors": {}}}
League Info
api.league_id("29000022")
Response
{"id": 0, "name": "string", "iconUrls": {"small": "string", "tiny": "string", "medium": "string"}}
Legend League Seasons
api.league_season("29000022")
Response
{"items": [{"id": "string"}], "paging": {"cursors": {}}}
Legend League Season Rankings
api.league_season_id("29000022", "2025-01", {"limit": 100})
Note: limit must be between 100 and 25,000.
Response
{"items": [{"tag": "string", "name": "string", "expLevel": 0, "trophies": 0, "attackWins": 0, "defenseWins": 0, "rank": 0, "clan": {"tag": "string", "name": "string", "badgeUrls": {}}, "leagueTier": {"id": 0, "name": "string", "iconUrls": {}}}], "paging": {"cursors": {}}}
Capital Leagues
api.capitalleagues()
api.capitalleagues_id("85000000")
Response
{"items": [{"id": 0, "name": "string"}], "paging": {"cursors": {}}}
Builder Base Leagues
api.builderbaseleagues()
api.builderbaseleagues_id("44000000")
Response
{"items": [{"id": 0, "name": "string"}], "paging": {"cursors": {}}}
League Tiers
api.leaguetiers()
api.leaguetiers_id("105000001")
Response
{"items": [{"id": 0, "name": "string", "iconUrls": {"small": "string", "large": "string"}}], "paging": {"cursors": {}}}
War Leagues
api.warleagues()
api.warleagues_id("48000000")
Response
{"items": [{"id": 0, "name": "string"}], "paging": {"cursors": {}}}
Gold Pass
api.goldpass()
Response
{"startTime": "string", "endTime": "string"}
Labels
Clan Labels
api.labels_clans()
Response
{"items": [{"id": 0, "name": "string", "iconUrls": {}}], "paging": {"cursors": {}}}
Player Labels
api.labels_players()
Response
{"items": [{"id": 0, "name": "string", "iconUrls": {}}], "paging": {"cursors": {}}}
Error Handling
All endpoints return a dict. Errors have this structure:
result = api.clan_tag("#INVALID")
if result.get("result") == "error":
print(result["message"]) # Human-readable message
print(result["error_type"]) # "timeout", "connection", "http", "json", "retry_exhausted", "unknown"
print(result.get("http_status")) # HTTP status code (present for "http" error_type)
Pydantic Models
When use_pydantic_models=True (requires pip install 'cocapi[pydantic]'), endpoints return typed model objects instead of dicts:
from cocapi import CocApi, ApiConfig
config = ApiConfig(use_pydantic_models=True)
api = CocApi("token", config=config)
clan = api.clan_tag("#2PP")
print(clan.name) # Attribute access with IDE autocompletion
print(clan.clanLevel)
player = api.players("#TAG")
print(player.trophies)
Available models: Clan, Player, ClanMember, League, Location, Achievement, ClanWar, GoldPassSeason, and more. Import directly from cocapi.
Credits
cocapi is not affiliated with SuperCell.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cocapi-4.0.0.tar.gz.
File metadata
- Download URL: cocapi-4.0.0.tar.gz
- Upload date:
- Size: 105.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5660b654a9997f75aa381c14b3eec78a3f9beb349579171e4e1ed80f4b3e341c
|
|
| MD5 |
f01e3652a7a7a56aa5cad39b9ee04231
|
|
| BLAKE2b-256 |
ae7052a75be4d31a864ab4b112b353307c70d6b9f2e97781df3a7fd3663669e9
|
File details
Details for the file cocapi-4.0.0-py3-none-any.whl.
File metadata
- Download URL: cocapi-4.0.0-py3-none-any.whl
- Upload date:
- Size: 73.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81a4f4a5b9b1cd8163656064d611f0f0123f73bb8114663d3b6ee5e288e17bc5
|
|
| MD5 |
2429d84dfe00e10ec2464705fb6ba245
|
|
| BLAKE2b-256 |
c8a105e02236eced23ecc9da5032078247593ba09b41e14d27322f5b658876c0
|