Skip to main content

PythonPRC

ItsThatOneJack, Copyright, All Rights Reserved Unless Stated Otherwise. Follow the license!

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.

Metadata

Release files for pythonprc 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pythonprc 2.0.0
File Size Uploaded
pythonprc-2.0.0.tar.gz 81.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pythonprc 2.0.0
File Interpreter ABI Platform
pythonprc-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 110.0 kB

Release files / pythonprc-2.0.0.tar.gz

Download URL pythonprc-2.0.0.tar.gz
Size 81.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6e20f5f536b14586ced0d6684e3aee6802bcbd40a8c06cf19bc7881f4dc4af45
BLAKE2b-256 checksum
How to use checksums
1100ebdbcb1687d3213ffd16cc9f63e7471e44bf87e521d7016349ea7f2375f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.8

Release files / pythonprc-2.0.0-py3-none-any.whl

Download URL pythonprc-2.0.0-py3-none-any.whl
Size 28.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e7f8957a9a9451bf331aeb9da02663c0c907a46e0a915b7653f989fd39956a5b
BLAKE2b-256 checksum
How to use checksums
72819ccd675404655758b15a74da902dc4c25878a6cb1d3cd4f2a35df0158498
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.8

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page