Skip to main content

Lightweight sync and async Python wrapper for the ER:LC PRC API

Project description

erlc-api

Lightweight Python wrapper for the ER:LC PRC API. Version 2 is a breaking, v2-first release with flat sync and async clients, typed dataclass responses by default, raw=True escape hatches, flexible commands, and explicit utility modules that only load when you import them.

Install And Extras

pip install erlc-api

Development install:

pip install -e .[dev]

Optional extras:

Extra Installs Used by
webhooks cryptography Event webhook Ed25519 signature verification
export openpyxl Exporter(...).xlsx(...)
time python-dateutil TimeTools().parse(..., enhanced=True)
rich rich Formatter().rich_table(...)
scheduling apscheduler Advanced scheduling integrations around watchers
utils all utility extras Export, time, rich, and scheduling helpers
all webhooks plus utility extras Everything optional

Example:

pip install "erlc-api[webhooks,export]"

Quickstart

Async apps and bots:

import asyncio
from erlc_api import AsyncERLC, cmd


async def main() -> None:
    async with AsyncERLC("server-key") as api:
        bundle = await api.server(players=True, queue=True, staff=True)
        result = await api.command(cmd.h("Hello from the API"))

        print(bundle.name, len(bundle.players or []), result.message)


asyncio.run(main())

Sync scripts:

from erlc_api import ERLC

with ERLC("server-key") as api:
    players = api.players()
    result = api.command("h Hello")
    print(len(players), result.message)

Client Reference

AsyncERLC is for async frameworks, Discord bots, FastAPI apps, background workers, and anything already running an event loop.

AsyncERLC(
    server_key: str | None = None,
    *,
    global_key: str | None = None,
    base_url: str = "https://api.policeroleplay.community",
    timeout_s: float = 20.0,
    retry_429: bool = True,
    user_agent: str | None = None,
)

Use it as an async context manager, or call await api.start() and await api.close() yourself.

ERLC has the same constructor and method names for sync scripts:

ERLC(
    server_key: str | None = None,
    *,
    global_key: str | None = None,
    base_url: str = "https://api.policeroleplay.community",
    timeout_s: float = 20.0,
    retry_429: bool = True,
    user_agent: str | None = None,
)

Every request sends Server-Key. If global_key= is configured, requests also send Authorization.

Every endpoint method accepts server_key= so one client can work with multiple servers:

api = ERLC("primary-server-key")

primary = api.players()
secondary = api.players(server_key="secondary-server-key")

validate_key() and health_check() return ValidationResult instead of raising common API errors.

Endpoint Methods

Typed models are returned by default. Pass raw=True to receive the exact JSON payload returned by PRC.

Method PRC endpoint Default return type Notes
server(...) GET /v2/server ServerBundle Accepts include flags for v2 sections
players() GET /v2/server?Players=true list[Player] Parses PlayerName:Id
staff() GET /v2/server?Staff=true StaffList Staff object maps plus .members()
queue() GET /v2/server?Queue=true list[int] Queue user IDs in API order
join_logs() GET /v2/server?JoinLogs=true list[JoinLogEntry] Includes join/leave flag and timestamp
kill_logs() GET /v2/server?KillLogs=true list[KillLogEntry] Includes killer/victim helpers
command_logs() GET /v2/server?CommandLogs=true list[CommandLogEntry] Useful with Finder and Analyzer
mod_calls() GET /v2/server?ModCalls=true list[ModCallEntry] Includes caller/moderator helpers
emergency_calls() GET /v2/server?EmergencyCalls=true list[EmergencyCall] v2 emergency call payloads
vehicles() GET /v2/server?Vehicles=true list[Vehicle] Vehicle model, owner, plate, color
bans() GET /v1/server/bans BanList Uses v1 because v2 does not replace it
command(command, ...) POST /v2/server/command CommandResult Accepts strings or cmd values
request(method, path, ...) Any path raw JSON/text Low-level escape hatch

server() include options:

bundle = await api.server(players=True, queue=True, staff=True)
everything = await api.server(all=True)
custom = await api.server(include=["players", "vehicles"])
raw_payload = await api.server(all=True, raw=True)

Command API

Commands are intentionally flexible:

from erlc_api import cmd, normalize_command

await api.command(":h hi")
await api.command("h hi")
await api.command(cmd.h("hi"))
await api.command(cmd.pm("Player", "hello"))
await api.command(cmd("pm", "Player", "hello"))

assert normalize_command("h hi") == ":h hi"

Validation is minimal and predictable:

Rule Behavior
Leading colon missing Added automatically
Blank command Raises ValueError
Newline in command Raises ValueError
Missing command name Raises ValueError
:log Not blocked by the wrapper

Dry-run validates and returns a local CommandResult without sending HTTP:

preview = await api.command(cmd.pm("Player", "hello"), dry_run=True)
print(preview.raw["command"], preview.success)

Models

Models are frozen dataclasses. They preserve the original payload in .raw, unknown fields in .extra, and convert back to dictionaries with .to_dict().

Key models:

Model Returned by Useful fields
ServerInfo server() without sections name, owner_id, current_players, max_players
ServerBundle server() server fields plus optional players, staff, logs, queue, vehicles
Player players() player, name, user_id, permission, callsign, team, location
StaffList staff() co_owners, admins, mods, helpers, .members()
CommandLogEntry command_logs() player, name, user_id, timestamp, command
CommandResult command() message, success
players = await api.players()
first = players[0]

print(first.name, first.user_id)
print(first.extra)
print(first.to_dict())

Parse PRC PlayerName:Id strings directly:

from erlc_api import parse_player_identifier

name, user_id = parse_player_identifier("Avi:123")

Utility Modules

Utilities are explicit lazy modules. import erlc_api only imports clients, models, errors, and cmd.

Module Import Purpose
Find from erlc_api.find import Finder Look up players, staff, vehicles, logs, bans, and calls
Filter from erlc_api.filter import Filter Chain filters and return .all(), .first(), .count()
Sort from erlc_api.sort import Sorter Sort by name, timestamp, team, permission, queue position, vehicle fields
Group from erlc_api.group import Grouper Group by team, permission, role, owner, command, day, hour
Diff from erlc_api.diff import Differ Compare lists or full server bundles
Wait from erlc_api.wait import AsyncWaiter, Waiter Poll until joins, leaves, queue changes, logs, or counts occur
Watch from erlc_api.watch import AsyncWatcher, Watcher Stream snapshot diffs as events and callbacks
Format from erlc_api.format import Formatter Compact Discord-safe, console-safe, and rich text formatting
Analytics from erlc_api.analytics import Analyzer Dashboard summaries, distributions, command usage, moderation trends
Export from erlc_api.export import Exporter JSON, CSV, Markdown, HTML, optional XLSX
Moderation from erlc_api.moderation import AsyncModerator, Moderator Safe command composition, previews, audit messages
Time from erlc_api.time import TimeTools Timestamp parsing, age strings, windows, timezone formatting
Schema from erlc_api.schema import SchemaInspector Field discovery, raw/extra inspection, payload diagnostics

Example:

from erlc_api.find import Finder
from erlc_api.filter import Filter
from erlc_api.export import Exporter

bundle = await api.server(all=True)
player = Finder(bundle).player("Avi")
police = Filter(bundle.players or []).team("Police").all()
csv_text = Exporter(police).csv()

Errors

All wrapper exceptions inherit from ERLCError.

Exception Raised when
APIError Non-success response without a more specific mapping
BadRequestError Request payload, path, or params are invalid
AuthError Server key or global key is missing, invalid, banned, or unauthorized
PermissionDeniedError A valid key cannot access the resource
NotFoundError The requested API path/resource was not found
NetworkError Timeout, DNS, connection, or transport failure
RateLimitError PRC returns 429 or a rate-limit error code
InvalidCommandError Command syntax/payload is rejected by PRC
RestrictedCommandError PRC restricts the command from API execution
ProhibitedMessageError Command text is prohibited by PRC
ServerOfflineError Server is offline or unavailable for the request
RobloxCommunicationError PRC cannot communicate with Roblox or the module
ModuleOutdatedError In-game module must be updated
ModelDecodeError Typed decoding received an unexpected payload shape
from erlc_api import ERLCError, RateLimitError

try:
    players = await api.players()
except RateLimitError as exc:
    print(exc.retry_after, exc.reset_epoch_s, exc.bucket)
except ERLCError as exc:
    print(exc.status_code, exc.error_code, exc.body_excerpt)

Rate Limits

On 429, RateLimitError exposes:

Attribute Meaning
retry_after / retry_after_s Seconds to wait when PRC provides Retry-After or body retry data
reset_epoch_s Epoch reset time parsed from X-RateLimit-Reset
bucket Bucket name from X-RateLimit-Bucket
error_code PRC error code when present

By default retry_429=True, so the transport sleeps once and retries once when it has retry timing. Set retry_429=False to handle rate limits yourself.

Wiki Deep Dives

The README is the compact API reference. The full GitHub Wiki source lives in docs/wiki:

Development

$env:PYTHONPATH = "src"
python -m pytest -q
python -m ruff check src tests scripts

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

erlc_api_py-2.0.0.tar.gz (51.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

erlc_api_py-2.0.0-py3-none-any.whl (50.8 kB view details)

Uploaded Python 3

File details

Details for the file erlc_api_py-2.0.0.tar.gz.

File metadata

  • Download URL: erlc_api_py-2.0.0.tar.gz
  • Upload date:
  • Size: 51.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for erlc_api_py-2.0.0.tar.gz
Algorithm Hash digest
SHA256 e31c18a8f8c7d13098b9be9f2eddb2cdc7ad0cda2404e94f395df269b452a963
MD5 63a6f7d0fcccc5ea95aa3f634836b9e9
BLAKE2b-256 a2d0a90ef1392cdacff49316db717e30b0dd743784572c2957e52ec4fa4c4d63

See more details on using hashes here.

File details

Details for the file erlc_api_py-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: erlc_api_py-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 50.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for erlc_api_py-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1de02d5c091aa0c0bce2b22a7bf96ef15d0d4a4393094bea7285cc8a306db197
MD5 5d5e01b4c067d1bcb1df01008ec4aee1
BLAKE2b-256 e37bd80e7526081296074244fc9c529bf3dc782e6ca57e37246050b74caf7fda

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page