The programmatic client¶
CgClient is an async, fully typed wrapper over CodinGame's private web API. Every request and
response is a dataclass; every endpoint the project knows about is a method with a docstring saying
what it does and, where relevant, what was confirmed live rather than assumed.
import asyncio
from codingame_tools.client import CgClient
async def main() -> None:
async with CgClient() as client:
pending = await client.services.contribution.get_all_pending_contributions()
for c in pending[:5]:
print(c.public_handle, c.title)
asyncio.run(main())
Authentication¶
The client reuses the same credentials and profiles the CLI does, so a
cg login in your shell is all a script needs:
async with CgClient() as client: # default profile
...
async with CgClient(profile="dev") as client:
...
Credentials are resolved best-effort at first use — available ones are applied, and nothing raises
just because there aren't any. That's deliberate: some endpoints are public, so the server gets to
decide rather than the client refusing up front. Methods that genuinely require a login raise
CgAuthenticationError.
There is no synchronous client and none is planned. Everything is async.
Layout¶
client.services.<service>.<method>() typed endpoint wrappers
client.services.<service>.helper.<...> retries, polling, multi-call workflows
client.servlets.file_servlet(...) file download
client.servlets.file_upload(...) file upload
- Services — what each of the 22 service endpoints covers.
- Files — uploading and downloading binary content.
Errors¶
CgAuthenticationError |
Not logged in, and couldn't implicitly log in. |
CgClientHttpError |
Transport failure, non-2xx status, or an undecodable response. Carries status_code. |
CgClientHttpError.status_code == 524 deserves special mention: CodinGame's CDN cuts off requests
that take too long at the origin, and for heavy contribution updates the operation often succeeded
anyway. The helper layer handles this by polling until the version increments rather than
propagating the error. If you call the plain service method yourself, you own that problem.
Schemas and unknown fields¶
Every response is parsed into a dataclass. Fields the client doesn't know about are captured in
extra_data rather than being dropped or raising — the API is undocumented and changes without
notice, so tolerating additions is the only workable stance.
The reverse isn't tolerated: a field the client believes is required, which the server then omits,
is a hard error naming the field. That's intentional. It surfaces protocol drift as a clear message
instead of a None propagating somewhere strange, and those reports are how most of the schema has
been corrected. If you hit one, it's a bug worth filing — usually a one-line fix making the field
optional.
Raw requests¶
For an endpoint the client doesn't wrap yet:
raw = await client.services.contribution.service_request("findContribution", [handle, True])
Returns parsed JSON with no schema applied. The CLI equivalent is cg raw-api, and it's how new
endpoints get mapped before they're wrapped.