Clients and Authentication¶
erlc-api.py v3 exposes two public clients with short aliases:
AsyncClient/AsyncERLCfor async apps, bots, web backends, and workers.Client/ERLCfor sync scripts and command-line tools.
Both clients use the same flat method names. The only difference is whether you
await the call.
Sync vs Async: Which Client to Use¶
Use AsyncClient / AsyncERLC when:
- you are writing a Discord bot, a FastAPI or Starlette web app, or any other
code that already runs inside an
asyncioevent loop; - you want to interleave multiple API calls concurrently using
asyncio.gather.
Use Client / ERLC when:
- you are writing a standalone script, a CLI tool, or a cron job;
- there is no event loop in scope and you do not need concurrency.
| Situation | Use |
|---|---|
| Standalone script or CLI | Client / ERLC |
| Discord bot (discord.py, nextcord, disnake) | AsyncClient / AsyncERLC |
| FastAPI / Starlette / aiohttp web app | AsyncClient / AsyncERLC |
| Celery worker, background thread | Client / ERLC |
asyncio script with asyncio.run(main()) |
AsyncClient / AsyncERLC |
Never call the sync client from inside an async function. Sync endpoint
methods call httpx.Client.send(), which blocks the thread. Inside a
discord.py command or a FastAPI route handler, that blocks the entire event
loop.
Never call asyncio.run() inside an already-running event loop. If you are
in a Jupyter notebook or an async context, use await with AsyncClient instead.
AsyncERLC¶
Signature:
AsyncERLC(
server_key: str | None = None,
*,
global_key: str | None = None,
base_url: str = "https://api.erlc.gg",
timeout_s: float = 20.0,
retry_429: bool = True,
rate_limited: bool = True,
user_agent: str | None = None,
)
Purpose: async PRC API client using httpx.AsyncClient.
Return type: client object. Endpoint methods return typed models by default.
Minimal example:
from erlc_api import AsyncERLC
async with AsyncERLC("server-key") as api:
players = await api.players()
print(players)
Alias:
from erlc_api import AsyncClient
async with AsyncClient.from_env() as client:
players = await client.players()
Important options:
| Option | Purpose |
|---|---|
server_key |
Default PRC private server key. Can be omitted only if each call passes server_key=. |
global_key |
Optional large-app/global API key. Sent as Authorization. |
base_url |
Override API base URL for tests or custom gateways. |
timeout_s |
HTTP timeout in seconds. |
retry_429 |
Sleep once and retry once on rate limits when retry timing is available. |
rate_limited |
Enable dynamic pre-request throttling based on PRC rate-limit headers. Defaults to True; pass False to opt out. |
user_agent |
Override default erlc-api-python/<version> user agent. |
Common mistakes:
- Creating a long-lived
AsyncERLCwithout closing it. Useasync with, or callawait api.close(). - Calling async methods from sync code without an event loop. Use
ERLCfor sync scripts. - Passing the global key as
server_key. The global key belongs inglobal_key=.
ERLC¶
Signature:
ERLC(
server_key: str | None = None,
*,
global_key: str | None = None,
base_url: str = "https://api.erlc.gg",
timeout_s: float = 20.0,
retry_429: bool = True,
rate_limited: bool = True,
user_agent: str | None = None,
)
Purpose: sync PRC API client using httpx.Client.
Return type: client object. Endpoint methods return typed models by default.
Minimal example:
Alias:
Common mistakes:
- Using
ERLCinside an async Discord or FastAPI handler. UseAsyncERLCthere. - Forgetting to use
with ERLC(...)for scripts that make several calls.
Environment Constructors¶
Both clients expose from_env(...):
Defaults:
| Variable | Purpose |
|---|---|
ERLC_SERVER_KEY |
Required server key. |
ERLC_GLOBAL_KEY |
Optional global Authorization key. |
Explicit server_key= and global_key= arguments override environment values.
Missing or blank server keys raise ValueError before HTTP.
Example:
Lifecycle¶
Async lifecycle:
api = AsyncERLC("server-key")
await api.start()
try:
players = await api.players()
finally:
await api.close()
Sync lifecycle:
Endpoint methods auto-start the underlying HTTP client if needed. Explicit startup is still useful for app startup hooks and repeated calls.
Dynamic Rate Limiting¶
retry_429=True reacts after PRC returns a rate limit. rate_limited=True is
the default and learns from successful and failed response headers before
avoidable requests:
Pass rate_limited=False only when your application already coordinates rate
limits outside the wrapper.
api.rate_limits returns a RateLimitSnapshot when dynamic limiting is enabled,
or None when it is disabled. Requests made with a global_key are tracked
under the "global" scope; server-key-only requests are tracked under
"server". The two scopes are independent and never share state.
Authentication Headers¶
Every server request sends:
When global_key= is configured, requests also send:
Custom headers passed to request(..., headers=...) cannot overwrite
Server-Key or Authorization; the client keeps those under its control.
Per-call Server Key Override¶
Every endpoint method accepts server_key=.
Async:
async with AsyncERLC("main-key") as api:
main = await api.players()
event = await api.players(server_key="event-server-key")
Sync:
with ERLC("main-key") as api:
main = api.players()
event = api.players(server_key="event-server-key")
Use this for multi-server dashboards and bots. If neither the client nor the
method has a server key, the client raises ValueError before sending HTTP.
Low-level Request¶
Async signature:
await api.request(
method: str,
path: str,
*,
server_key: str | None = None,
params: Mapping[str, Any] | None = None,
json: Any = None,
headers: Mapping[str, str] | None = None,
) -> Any
Sync signature:
api.request(
method: str,
path: str,
*,
server_key: str | None = None,
params: Mapping[str, Any] | None = None,
json: Any = None,
headers: Mapping[str, str] | None = None,
) -> Any
Purpose: raw escape hatch for newly added PRC endpoints or custom tests.
Minimal example:
Return type: decoded JSON when possible, response text for non-JSON responses,
or None for empty responses.
Common mistakes:
- Passing a path without a leading slash when your
base_urlexpects one. - Expecting typed dataclasses from
request; this method intentionally returns raw data.
Validation Helpers¶
validate_key(*, server_key: str | None = None) -> ValidationResult
Purpose: check whether a key can call server(raw=True) without raising common
API errors.
Return type:
ValidationResult(
status: ValidationStatus,
retry_after: float | None = None,
api_status: int | None = None,
)
Statuses:
| Status | Meaning |
|---|---|
ValidationStatus.OK |
Request succeeded. |
ValidationStatus.AUTH_ERROR |
Auth failed. |
ValidationStatus.RATE_LIMITED |
PRC returned a rate limit. |
ValidationStatus.NETWORK_ERROR |
Transport failed. |
ValidationStatus.API_ERROR |
PRC returned another API error. |
health_check() is an alias for validate_key().
Async:
Sync: