{"id":49570764,"url":"https://github.com/fortune1243/erlc-api","last_synced_at":"2026-05-03T14:01:20.103Z","repository":{"id":342769036,"uuid":"1173721192","full_name":"Fortune1243/erlc-api","owner":"Fortune1243","description":"The most sophisticated open source API wrapper for ER:LC.","archived":false,"fork":false,"pushed_at":"2026-04-10T12:09:54.000Z","size":278,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-10T12:33:54.529Z","etag":null,"topics":["api","bot","discord","erlc","erlc-api","erlc-bot","erlcapi","roblox"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Fortune1243.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T17:17:39.000Z","updated_at":"2026-04-10T12:09:58.000Z","dependencies_parsed_at":"2026-05-03T14:00:36.763Z","dependency_job_id":null,"html_url":"https://github.com/Fortune1243/erlc-api","commit_stats":null,"previous_names":["fortune1243/erlc-api"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/Fortune1243/erlc-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fortune1243%2Ferlc-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fortune1243%2Ferlc-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fortune1243%2Ferlc-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fortune1243%2Ferlc-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Fortune1243","download_url":"https://codeload.github.com/Fortune1243/erlc-api/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fortune1243%2Ferlc-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32571456,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T06:36:36.687Z","status":"ssl_error","status_checked_at":"2026-05-03T06:36:09.306Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","bot","discord","erlc","erlc-api","erlc-bot","erlcapi","roblox"],"created_at":"2026-05-03T14:00:29.992Z","updated_at":"2026-05-03T14:01:20.093Z","avatar_url":"https://github.com/Fortune1243.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# erlc-api\n\nLightweight Python wrapper for the **ER:LC PRC API**. Version 2 is a breaking,\nv2-first release with flat sync and async clients, typed dataclass responses by\ndefault, `raw=True` escape hatches, flexible commands, and explicit utility\nmodules that only load when you import them.\n\n## Install And Extras\n\n```bash\npip install erlc-api.py\n```\n\nDevelopment install:\n\n```bash\npip install -e .[dev]\n```\n\nOptional extras:\n\n| Extra | Installs | Used by |\n| --- | --- | --- |\n| `webhooks` | `cryptography` | Event webhook Ed25519 signature verification |\n| `export` | `openpyxl` | `Exporter(...).xlsx(...)` |\n| `time` | `python-dateutil` | `TimeTools().parse(..., enhanced=True)` |\n| `rich` | `rich` | `Formatter().rich_table(...)` |\n| `scheduling` | `apscheduler` | Advanced scheduling integrations around watchers |\n| `utils` | all utility extras | Export, time, rich, and scheduling helpers |\n| `all` | webhooks plus utility extras | Everything optional |\n\nExample:\n\n```bash\npip install \"erlc-api.py[webhooks,export]\"\n```\n\n## Quickstart\n\nAsync apps and bots:\n\n```python\nimport asyncio\nfrom erlc_api import AsyncERLC, cmd\n\n\nasync def main() -\u003e None:\n    async with AsyncERLC(\"server-key\") as api:\n        bundle = await api.server(players=True, queue=True, staff=True)\n        result = await api.command(cmd.h(\"Hello from the API\"))\n\n        print(bundle.name, len(bundle.players or []), result.message)\n\n\nasyncio.run(main())\n```\n\nSync scripts:\n\n```python\nfrom erlc_api import ERLC\n\nwith ERLC(\"server-key\") as api:\n    players = api.players()\n    result = api.command(\"h Hello\")\n    print(len(players), result.message)\n```\n\n## Client Reference\n\n`AsyncERLC` is for async frameworks, Discord bots, FastAPI apps, background\nworkers, and anything already running an event loop.\n\n```python\nAsyncERLC(\n    server_key: str | None = None,\n    *,\n    global_key: str | None = None,\n    base_url: str = \"https://api.policeroleplay.community\",\n    timeout_s: float = 20.0,\n    retry_429: bool = True,\n    user_agent: str | None = None,\n)\n```\n\nUse it as an async context manager, or call `await api.start()` and\n`await api.close()` yourself.\n\n`ERLC` has the same constructor and method names for sync scripts:\n\n```python\nERLC(\n    server_key: str | None = None,\n    *,\n    global_key: str | None = None,\n    base_url: str = \"https://api.policeroleplay.community\",\n    timeout_s: float = 20.0,\n    retry_429: bool = True,\n    user_agent: str | None = None,\n)\n```\n\nEvery request sends `Server-Key`. If `global_key=` is configured, requests also\nsend `Authorization`.\n\nEvery endpoint method accepts `server_key=` so one client can work with multiple\nservers:\n\n```python\napi = ERLC(\"primary-server-key\")\n\nprimary = api.players()\nsecondary = api.players(server_key=\"secondary-server-key\")\n```\n\n`validate_key()` and `health_check()` return `ValidationResult` instead of\nraising common API errors.\n\n## Endpoint Methods\n\nTyped models are returned by default. Pass `raw=True` to receive the exact JSON\npayload returned by PRC.\n\n| Method | PRC endpoint | Default return type | Notes |\n| --- | --- | --- | --- |\n| `server(...)` | `GET /v2/server` | `ServerBundle` | Accepts include flags for v2 sections |\n| `players()` | `GET /v2/server?Players=true` | `list[Player]` | Parses `PlayerName:Id` |\n| `staff()` | `GET /v2/server?Staff=true` | `StaffList` | Staff object maps plus `.members()` |\n| `queue()` | `GET /v2/server?Queue=true` | `list[int]` | Queue user IDs in API order |\n| `join_logs()` | `GET /v2/server?JoinLogs=true` | `list[JoinLogEntry]` | Includes join/leave flag and timestamp |\n| `kill_logs()` | `GET /v2/server?KillLogs=true` | `list[KillLogEntry]` | Includes killer/victim helpers |\n| `command_logs()` | `GET /v2/server?CommandLogs=true` | `list[CommandLogEntry]` | Useful with `Finder` and `Analyzer` |\n| `mod_calls()` | `GET /v2/server?ModCalls=true` | `list[ModCallEntry]` | Includes caller/moderator helpers |\n| `emergency_calls()` | `GET /v2/server?EmergencyCalls=true` | `list[EmergencyCall]` | v2 emergency call payloads |\n| `vehicles()` | `GET /v2/server?Vehicles=true` | `list[Vehicle]` | Vehicle model, owner, plate, color |\n| `bans()` | `GET /v1/server/bans` | `BanList` | Uses v1 because v2 does not replace it |\n| `command(command, ...)` | `POST /v2/server/command` | `CommandResult` | Accepts strings or `cmd` values |\n| `request(method, path, ...)` | Any path | raw JSON/text | Low-level escape hatch |\n\n`server()` include options:\n\n```python\nbundle = await api.server(players=True, queue=True, staff=True)\neverything = await api.server(all=True)\ncustom = await api.server(include=[\"players\", \"vehicles\"])\nraw_payload = await api.server(all=True, raw=True)\n```\n\n## Command API\n\nCommands are intentionally flexible:\n\n```python\nfrom erlc_api import cmd, normalize_command\n\nawait api.command(\":h hi\")\nawait api.command(\"h hi\")\nawait api.command(cmd.h(\"hi\"))\nawait api.command(cmd.pm(\"Player\", \"hello\"))\nawait api.command(cmd(\"pm\", \"Player\", \"hello\"))\n\nassert normalize_command(\"h hi\") == \":h hi\"\n```\n\nValidation is minimal and predictable:\n\n| Rule | Behavior |\n| --- | --- |\n| Leading colon missing | Added automatically |\n| Blank command | Raises `ValueError` |\n| Newline in command | Raises `ValueError` |\n| Missing command name | Raises `ValueError` |\n| `:log` | Not blocked by the wrapper |\n\nDry-run validates and returns a local `CommandResult` without sending HTTP:\n\n```python\npreview = await api.command(cmd.pm(\"Player\", \"hello\"), dry_run=True)\nprint(preview.raw[\"command\"], preview.success)\n```\n\n## Models\n\nModels are frozen dataclasses. They preserve the original payload in `.raw`,\nunknown fields in `.extra`, and convert back to dictionaries with `.to_dict()`.\n\nKey models:\n\n| Model | Returned by | Useful fields |\n| --- | --- | --- |\n| `ServerInfo` | `server()` without sections | `name`, `owner_id`, `current_players`, `max_players` |\n| `ServerBundle` | `server()` | server fields plus optional `players`, `staff`, logs, queue, vehicles |\n| `Player` | `players()` | `player`, `name`, `user_id`, `permission`, `callsign`, `team`, `location` |\n| `StaffList` | `staff()` | `co_owners`, `admins`, `mods`, `helpers`, `.members()` |\n| `CommandLogEntry` | `command_logs()` | `player`, `name`, `user_id`, `timestamp`, `command` |\n| `CommandResult` | `command()` | `message`, `success` |\n\n```python\nplayers = await api.players()\nfirst = players[0]\n\nprint(first.name, first.user_id)\nprint(first.extra)\nprint(first.to_dict())\n```\n\nParse PRC `PlayerName:Id` strings directly:\n\n```python\nfrom erlc_api import parse_player_identifier\n\nname, user_id = parse_player_identifier(\"Avi:123\")\n```\n\n## Utility Modules\n\nUtilities are explicit lazy modules. `import erlc_api` only imports clients,\nmodels, errors, and `cmd`.\n\n| Module | Import | Purpose |\n| --- | --- | --- |\n| Find | `from erlc_api.find import Finder` | Look up players, staff, vehicles, logs, bans, and calls |\n| Filter | `from erlc_api.filter import Filter` | Chain filters and return `.all()`, `.first()`, `.count()` |\n| Sort | `from erlc_api.sort import Sorter` | Sort by name, timestamp, team, permission, queue position, vehicle fields |\n| Group | `from erlc_api.group import Grouper` | Group by team, permission, role, owner, command, day, hour |\n| Diff | `from erlc_api.diff import Differ` | Compare lists or full server bundles |\n| Wait | `from erlc_api.wait import AsyncWaiter, Waiter` | Poll until joins, leaves, queue changes, logs, or counts occur |\n| Watch | `from erlc_api.watch import AsyncWatcher, Watcher` | Stream snapshot diffs as events and callbacks |\n| Format | `from erlc_api.format import Formatter` | Compact Discord-safe, console-safe, and rich text formatting |\n| Analytics | `from erlc_api.analytics import Analyzer` | Dashboard summaries, distributions, command usage, moderation trends |\n| Export | `from erlc_api.export import Exporter` | JSON, CSV, Markdown, HTML, optional XLSX |\n| Moderation | `from erlc_api.moderation import AsyncModerator, Moderator` | Safe command composition, previews, audit messages |\n| Time | `from erlc_api.time import TimeTools` | Timestamp parsing, age strings, windows, timezone formatting |\n| Schema | `from erlc_api.schema import SchemaInspector` | Field discovery, raw/extra inspection, payload diagnostics |\n\nExample:\n\n```python\nfrom erlc_api.find import Finder\nfrom erlc_api.filter import Filter\nfrom erlc_api.export import Exporter\n\nbundle = await api.server(all=True)\nplayer = Finder(bundle).player(\"Avi\")\npolice = Filter(bundle.players or []).team(\"Police\").all()\ncsv_text = Exporter(police).csv()\n```\n\n## Errors\n\nAll wrapper exceptions inherit from `ERLCError`.\n\n| Exception | Raised when |\n| --- | --- |\n| `APIError` | Non-success response without a more specific mapping |\n| `BadRequestError` | Request payload, path, or params are invalid |\n| `AuthError` | Server key or global key is missing, invalid, banned, or unauthorized |\n| `PermissionDeniedError` | A valid key cannot access the resource |\n| `NotFoundError` | The requested API path/resource was not found |\n| `NetworkError` | Timeout, DNS, connection, or transport failure |\n| `RateLimitError` | PRC returns `429` or a rate-limit error code |\n| `InvalidCommandError` | Command syntax/payload is rejected by PRC |\n| `RestrictedCommandError` | PRC restricts the command from API execution |\n| `ProhibitedMessageError` | Command text is prohibited by PRC |\n| `ServerOfflineError` | Server is offline or unavailable for the request |\n| `RobloxCommunicationError` | PRC cannot communicate with Roblox or the module |\n| `ModuleOutdatedError` | In-game module must be updated |\n| `ModelDecodeError` | Typed decoding received an unexpected payload shape |\n\n```python\nfrom erlc_api import ERLCError, RateLimitError\n\ntry:\n    players = await api.players()\nexcept RateLimitError as exc:\n    print(exc.retry_after, exc.reset_epoch_s, exc.bucket)\nexcept ERLCError as exc:\n    print(exc.status_code, exc.error_code, exc.body_excerpt)\n```\n\n## Rate Limits\n\nOn `429`, `RateLimitError` exposes:\n\n| Attribute | Meaning |\n| --- | --- |\n| `retry_after` / `retry_after_s` | Seconds to wait when PRC provides `Retry-After` or body retry data |\n| `reset_epoch_s` | Epoch reset time parsed from `X-RateLimit-Reset` |\n| `bucket` | Bucket name from `X-RateLimit-Bucket` |\n| `error_code` | PRC error code when present |\n\nBy default `retry_429=True`, so the transport sleeps once and retries once when\nit has retry timing. Set `retry_429=False` to handle rate limits yourself.\n\n## Wiki Deep Dives\n\nThe README is the compact API reference. The full GitHub Wiki source lives in\n`docs/wiki`:\n\n- [Clients and Authentication](docs/wiki/Clients-and-Authentication.md)\n- [Endpoint Reference](docs/wiki/Endpoint-Reference.md)\n- [Models Reference](docs/wiki/Models-Reference.md)\n- [Commands Reference](docs/wiki/Commands-Reference.md)\n- [Utilities Reference](docs/wiki/Utilities-Reference.md)\n- [Waiters and Watchers](docs/wiki/Waiters-and-Watchers.md)\n- [Formatting, Analytics, and Export](docs/wiki/Formatting-Analytics-and-Export.md)\n- [Moderation Helpers](docs/wiki/Moderation-Helpers.md)\n- [Webhooks Reference](docs/wiki/Webhooks-Reference.md)\n- [Errors and Rate Limits](docs/wiki/Errors-and-Rate-Limits.md)\n- [Migration to v2](docs/wiki/Migration-to-v2.md)\n\n## Development\n\n```powershell\n$env:PYTHONPATH = \"src\"\npython -m pytest -q\npython -m ruff check src tests scripts\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffortune1243%2Ferlc-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffortune1243%2Ferlc-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffortune1243%2Ferlc-api/lists"}