Skip to main content

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

Project description

erlc-api.py

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.py

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.py[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
Snapshot from erlc_api.snapshot import SnapshotStore JSONL snapshot persistence and latest-state comparisons
Audit from erlc_api.audit import AuditLog JSON-safe audit events for commands, webhooks, watchers, and moderation
Idempotency from erlc_api.idempotency import MemoryDeduper, FileDeduper TTL dedupe for webhook deliveries and watcher restarts
Limits from erlc_api.limits import poll_plan, safe_interval Conservative polling guidance without fake PRC limit claims
Custom Commands from erlc_api.custom_commands import CustomCommandRouter Framework-neutral router for PRC webhook messages starting with ;

Example:

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

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

Custom in-game commands are received through PRC Event Webhooks. Use erlc_api.webhooks for signature verification and erlc_api.custom_commands for flexible routing:

from erlc_api.custom_commands import CustomCommandRouter

router = CustomCommandRouter(prefix=";")


@router.command("ping", "p")
async def ping(ctx):
    return ctx.reply("pong")


result = await router.dispatch({"Message": ";p"})

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.

Documentation Deep Dives

The README is the compact API reference. The full documentation 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.1.0.tar.gz (61.1 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.1.0-py3-none-any.whl (59.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: erlc_api_py-2.1.0.tar.gz
  • Upload date:
  • Size: 61.1 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.1.0.tar.gz
Algorithm Hash digest
SHA256 a701617d432e53dd7329f7e23743647af5b3159d20bc50394ac460501dfec89a
MD5 e15a7e68d95522fc15858c5308d1fb39
BLAKE2b-256 d51c19b8095e58062ec25eb51e4b6b5cd3f5947a18d403e3b29abeae1d7dc5f5

See more details on using hashes here.

File details

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

File metadata

  • Download URL: erlc_api_py-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 59.2 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c246b806cd56c98e7f2a6f396328429999b95d1aea9acf9f47d8108a088acc29
MD5 73f92683b75b74f7485e619d7fcbafee
BLAKE2b-256 a9df1884f31e99958ddf4250ea15ea3bf5648d2a54df70e04c3a24f3ca760bba

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