Skip to main content

An easy-to-use wrapper for PRC's ER:LC server API!

Project description

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.

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

pythonprc-1.0.0.tar.gz (80.0 kB view details)

Uploaded Source

Built Distribution

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

pythonprc-1.0.0-py3-none-any.whl (26.5 kB view details)

Uploaded Python 3

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

Hashes for pythonprc-1.0.0.tar.gz
Algorithm Hash digest
SHA256 c29550d128c0013768a3410fd999dac9e54921d502d456103eb529c1b9bb369a
MD5 6692f3b41a2d9d696ec41b4753489dca
BLAKE2b-256 3f2b7f7cf25f4a904e5b364dce3b6ad9f2967f4609f620d424df7216d8f7049d

See more details on using hashes here.

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

Hashes for pythonprc-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5e350d7677ab550fb0b29c5a6074819b03e1ae070b23403870f9e5ec48959b96
MD5 f4e2e11510b00e0e2cdbe3a4547a85f3
BLAKE2b-256 88da08d91d704ec4f70b24c1e1ac315e4af168368ec03e303c5f37d9ac3b2a27

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