An easy-to-use wrapper for PRC's ER:LC server API!
Project description
PythonPRC
An async Python wrapper for the PRC Private Server API — the API for ER:LC private servers.
Installation
pip install pythonprc
For webhook signature verification, you'll also need cryptography (installed by default) or optionally PyNaCl:
pip install "pythonprc[nacl]"
Quickstart
import asyncio
from pythonprc import PythonPRC
async def main():
client = PythonPRC("your-server-key")
info = await client.get_server(players=True)
print(f"{info.name}: {info.current_players}/{info.max_players} players")
for player in info.players:
print(player.username, player.team, player.location)
asyncio.run(main())
Authentication
All requests require a server key, obtained from your private server's settings in-game (Settings → search "API key"). Pass it as the first argument to PythonPRC.
If you have a global API key (issued by PRC for large-scale apps), pass it as global_api_key:
client = PythonPRC("your-server-key", global_api_key="your-global-key")
Client
PythonPRC(
server_key: str,
global_api_key: str | None = None,
max_retries: int = 3, # retries on 5xx / connection errors
backoff_base: float = 1.0, # base seconds for exponential backoff
)
The client exposes a single low-level method and several high-level convenience methods.
Low-level
await client.request(method, path, **kwargs)
Passes **kwargs directly to aiohttp. Handles rate limiting, backoff, and error raising automatically. Returns the parsed JSON body.
Rate limiting
The client reads X-RateLimit-* headers on every response and tracks state per bucket. If a bucket is exhausted it will proactively sleep before the next request rather than hitting a 429. On a 429 it raises RateLimitError with retry_after and bucket attributes.
Methods
get_server(**flags) → ServerInfo
Fetches server status. All sub-resources are opt-in:
info = await client.get_server(
players=True,
staff=True,
join_logs=True,
queue=True,
kill_logs=True,
command_logs=True,
mod_calls=True,
emergency_calls=True,
vehicles=True,
)
Only request what you need — each flag adds data to the response.
send_command(command: str) → str
Runs a command in-game as virtual server management. Returns the API's confirmation message.
await client.send_command(":h Welcome to the server!")
await client.send_command(":kick Username Reason")
Convenience methods
Each of these calls get_server with the relevant flag and returns the list directly:
await client.get_players() # list[Player]
await client.get_staff() # list[StaffMember]
await client.get_join_logs() # list[JoinLogEntry]
await client.get_queue() # list[int] (Roblox user IDs)
await client.get_kill_logs() # list[KillLogEntry]
await client.get_command_logs() # list[CommandLogEntry]
await client.get_mod_calls() # list[ModCall]
await client.get_emergency_calls() # list[EmergencyCall]
await client.get_vehicles() # list[Vehicle]
Models
ServerInfo
| Attribute | Type |
|---|---|
name |
str |
owner_id |
int |
co_owner_ids |
list[int] |
current_players |
int |
max_players |
int |
join_key |
str |
acc_verified_req |
str |
team_balance |
bool |
players |
list[Player] | None |
staff |
list[StaffMember] | None |
join_logs |
list[JoinLogEntry] | None |
queue |
list[int] | None |
kill_logs |
list[KillLogEntry] | None |
command_logs |
list[CommandLogEntry] | None |
mod_calls |
list[ModCall] | None |
emergency_calls |
list[EmergencyCall] | None |
vehicles |
list[Vehicle] | None |
Player
| Attribute | Type |
|---|---|
username |
str |
roblox_id |
int |
team |
Team |
permission |
Permission |
callsign |
str | None |
wanted_stars |
int |
location |
PlayerLocation | None |
is_wanted |
bool (property) |
PlayerLocation
| Attribute | Type |
|---|---|
x |
float |
z |
float |
postal_code |
str |
street_name |
str |
building_number |
str |
StaffMember
| Attribute | Type |
|---|---|
roblox_id |
int |
username |
str |
role |
Permission |
Vehicle
| Attribute | Type |
|---|---|
name |
str |
owner |
str |
plate |
str |
texture |
str | None |
color_hex |
str | None |
color_name |
str | None |
JoinLogEntry
| Attribute | Type |
|---|---|
player |
str |
timestamp |
int |
joined |
bool |
KillLogEntry
| Attribute | Type |
|---|---|
killer |
str |
killed |
str |
timestamp |
int |
CommandLogEntry
| Attribute | Type |
|---|---|
player |
str |
command |
str |
timestamp |
int |
ModCall
| Attribute | Type |
|---|---|
caller |
str |
moderator |
str | None |
timestamp |
int |
EmergencyCall
| Attribute | Type |
|---|---|
team |
str |
caller_id |
int |
player_ids |
list[int] |
position |
tuple[float, float] |
started_at |
int |
call_number |
int |
description |
str |
position_descriptor |
str |
Enums
Team
Civilian, Sheriff, Police, Fire, DOT, Unknown
Permission
Normal, Moderator, Admin, Server Owner, Unknown
Errors
All errors inherit from ERLCError.
| Exception | When |
|---|---|
ERLCError |
Base class for all errors |
RateLimitError |
HTTP 429 — has .retry_after (seconds) and .bucket |
AuthenticationError |
HTTP 403 / bad keys |
InvalidServerKeyError |
Invalid or expired server key specifically |
ServerOfflineError |
Server has no players (HTTP 422) |
CommandRestrictedError |
Command is restricted |
OutdatedModuleError |
In-game module is out of date |
from pythonprc import PythonPRC
from pythonprc.errors import RateLimitError, ServerOfflineError
try:
await client.send_command(":shutdown")
except ServerOfflineError:
print("No players in server")
except RateLimitError as e:
print(f"Rate limited, retry in {e.retry_after}s")
Webhooks
ER:LC can POST events to your endpoint. Every request is signed with Ed25519 and must be verified before acting on it.
Currently sent events: messages starting with ; (custom in-game commands) and emergency calls.
from pythonprc.webhook import verify_signature, WebhookVerificationError
# aiohttp example
async def handle_webhook(request):
raw_body = await request.read()
try:
verify_signature(
timestamp=request.headers["X-Signature-Timestamp"],
sig_hex=request.headers["X-Signature-Ed25519"],
raw_body=raw_body,
)
except WebhookVerificationError:
raise web.HTTPUnauthorized()
payload = await request.json()
...
To set up your webhook URL, go to your private server settings and search for Event Webhook. Paste an HTTPS URL that can receive JSON POST requests. Append ?long=true to receive extended payloads with type information.
License
GPL-3.0-only — see LICENSE.
pythonprc is not affiliated with or endorsed by PRC or the ER:LC team.
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 pythonprc-1.0.0.tar.gz.
File metadata
- Download URL: pythonprc-1.0.0.tar.gz
- Upload date:
- Size: 80.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c29550d128c0013768a3410fd999dac9e54921d502d456103eb529c1b9bb369a
|
|
| MD5 |
6692f3b41a2d9d696ec41b4753489dca
|
|
| BLAKE2b-256 |
3f2b7f7cf25f4a904e5b364dce3b6ad9f2967f4609f620d424df7216d8f7049d
|
File details
Details for the file pythonprc-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pythonprc-1.0.0-py3-none-any.whl
- Upload date:
- Size: 26.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e350d7677ab550fb0b29c5a6074819b03e1ae070b23403870f9e5ec48959b96
|
|
| MD5 |
f4e2e11510b00e0e2cdbe3a4547a85f3
|
|
| BLAKE2b-256 |
88da08d91d704ec4f70b24c1e1ac315e4af168368ec03e303c5f37d9ac3b2a27
|