Roleplay Projectapi.roleplayproject.net Edge online
01  Integration edge · private · machine to machine

The narrow door between our servers and the outside world.

This host exists so that Roleplay Project's own game servers, Discord and telemetry can talk to the platform over one deliberately small, heavily filtered surface. It is not a public developer API and no keys are issued to third parties.

  • Four routes are proxied. Every other path is refused at the edge before any application code runs.
  • Every request is either key-authenticated or cryptographically signed. Nothing here is anonymous.
  • Player-facing data lives on the UCP, not here.
Client, TLS edge, application and operations, connected in sequence CLIENT game server discord telemetry EDGE · NGINX tls 1.2+ / hsts path allowlist rate limits body caps header hardening ip rewrite /admin → 403 other → 404 APP · 127.0.0.1 key check ed25519 verify allowlist match json schema loopback only OPERATIONS alerts · logs · blocks https://api.roleplayproject.net · one hop in, one hop out
Fig. 1 · request pathrejections happen left to right, as early as possible
02  Traffic

Live request counter

Every request that reaches this host is counted at the edge, including the ones we refuse. Figures refresh every 20 seconds.

total requests—since the counter began
last 24 hours—— in the last hour · — in the last minute
accepted—answered 2xx or 3xx, all time
refused—401, 403, 404 or 429 at the edge or the app
requests per hour · last 24 h—
by route · last 24 h—
    03  Surface

    Everything this host will answer

    The whole public surface fits in one table. If a path is not listed, nginx returns 404 without contacting the application.

    MethodPathPurposeAuthLimitBody
    POST/v1/healthHeartbeat from an authorised game server.Registers the server, its addresses and version; unknown origins are flagged to operations.shared key10 r/s, burst 20256 KB
    POST/v1/eventOperational telemetry event.Structured events only. Free-form payloads are rejected.shared key10 r/s, burst 20256 KB
    GET/v1/configRuntime configuration for an authorised server.Read-only. Contents depend on the caller's standing.shared key10 r/s, burst 20256 KB
    POST/discord/interactionsDiscord interaction webhook.Only accepted with a valid Ed25519 signature from Discord over the timestamp and raw body.signature30 r/s, burst 102 MB
    ANY/admin/*Operator surface.Blocked at the edge with 403. Inside the application it is additionally bound to localhost, so a misconfigured proxy still cannot reach it.———
    ANY/*Everything else.404 from nginx. No application code runs. Plain HTTP on port 80 is also a flat 404, not a redirect.———
    04  Request path

    What happens to a request, in order

    Each stage can refuse a request. The further left a refusal happens, the less it costs us and the less an attacker learns.

    stage 1 · transport

    TLS terminates at the edge

    Certificates come from Let's Encrypt and renew automatically on a timer. HSTS is set with preload and subdomains, so browsers refuse to speak plain HTTP to this host at all.

    stage 2 · routing

    Path allowlist

    nginx only knows four locations. Anything else, including probes for admin panels, environment files and old CMS paths, ends at the edge with a 404 or 403 and is logged for the watchdog.

    stage 3 · throttling

    Per-address rate limits

    Two independent limit zones keyed on the client address, with small bursts. A misbehaving integration or a flood is slowed to a trickle before it reaches the application.

    stage 4 · identity

    Key or signature, never neither

    Telemetry routes require a shared secret header known only to our own servers. Discord traffic is verified against Discord's public key with Ed25519; unsigned or stale requests get 401.

    stage 5 · authorisation

    Allowlisted origins

    A valid key proves the caller has our software, not that it is allowed to run it. Reported and observed addresses are matched against an allowlist maintained by operations; unknown servers are surfaced to staff for a decision.

    stage 6 · handling

    Small, typed payloads

    Bodies are capped at the edge and parsed as JSON with strict limits. The application never trusts client-supplied forwarding headers: the real address is rewritten by nginx on the way in.

    05  Protection

    How we protect it, layer by layer

    Defence in depth: no single control is load-bearing. Each layer assumes the one before it has already failed.

    layerTransport
    • TLS only. Modern cipher configuration from the Let's Encrypt profile, separate DH parameters, HSTS max-age=31536000; includeSubDomains; preload.
    • No downgrade path. Port 80 returns 404 for this hostname rather than redirecting, so nothing sensitive is ever sent in the clear "by accident".
    layerEdge policy
    • Allowlist, not blocklist. Four proxied locations. Everything unknown is refused; there is no catch-all proxy to the application.
    • Rate limits per client. 10 r/s on /v1/* and 30 r/s on Discord, each with a small burst. Exceeding them returns 429 with our own error page, not a stack trace.
    • Body caps. 256 KB for telemetry, 2 MB for Discord, enforced before the request is proxied.
    • Hardened headers on every response. X-Frame-Options: DENY, nosniff, Referrer-Policy: no-referrer, Cross-Origin-Resource-Policy: same-site, a locked-down Permissions-Policy, and a Content-Security-Policy on this page with frame-ancestors 'none'.
    • Custom error pages. 403, 404, 429, 500, 502 and 503 are served from static files marked no-store, so error states leak nothing about the upstream.
    layerIdentity
    • Shared-secret header for telemetry routes, held only by our own game servers and rotated by operations. A wrong or missing key is a 403 with no further detail.
    • Ed25519 signatures for Discord: the signature and timestamp headers are verified against Discord's published public key over the raw request body before any JSON is parsed. Missing, invalid or unparsable signatures are 401.
    • Origin allowlist for game servers, kept outside the code and editable only from the host. Servers that present our software from an unknown address are recorded and flagged; they are never silently accepted.
    layerIsolation
    • Loopback binding. The application listens on 127.0.0.1 inside a container. The only way in is through nginx; there is no exposed port.
    • Secrets outside the image. Keys and configuration are mounted at run time from files readable only by root. Nothing secret is baked into the container or the repository.
    • Operator routes double-locked. /admin/* is refused by nginx and, independently, accepted by the application only from localhost.
    • Least privilege on the host. Each platform service runs in its own container with only the mounts it needs; this one has no access to the game database.
    layerDetection
    • Security watchdog. Every edge access log, this host's included, is ingested every 30 seconds. Sources that probe for exploit paths are scored and automatically blocked for a cooling-off period, with the block and the reason recorded.
    • Connection-flood monitor. A separate process samples connection counts per address every 5 seconds against warning and alert thresholds and pages operations in Discord when a flood begins.
    • Health checks. Platform API containers are health-checked continuously; an unhealthy service has its diagnostics captured and staff alerted before anyone restarts it.
    • Host hardening. Interactive SSH is key-only with fail2ban in front of it, and staff file-transfer accounts are chrooted to exactly the directories they need, with code hidden from them.
    layerRecovery
    • Nightly backups of configuration and data, every day at 04:00.
    • Rebuildable services. Everything here is a container built from source; a compromised or broken instance is replaced, not repaired in place.
    • Central configuration. Authorised servers pull their runtime configuration from this host, so operations can change how a deployment behaves without shipping code to it.
    proxied routes4everything else refused at the edge
    anonymous endpoints0key or signature on every request
    exposed ports443application bound to loopback only
    log ingestion30 swatchdog sweep interval
    06  Scope

    What this API is not

    Most people who land here were looking for something else. This should save you a support ticket.

    not a developer api

    No keys for third parties here

    The shared key on this host is issued to Roleplay Project's own infrastructure only. Requests for it are declined.

    Building something that signs people in with their RPP account? That is a different service: register an OAuth application in the UCP developer portal. You get a client id and secret, a standard authorisation-code flow against ucp.roleplayproject.net/api/oauth2, and users see exactly what your app may read before they approve it.

    not a data source

    No player or character data

    This host holds no player records. Characters, vehicles, properties, factions and support live on the User Control Panel, behind your own login, 2FA and audit trail.

    not the status page

    Uptime lives elsewhere

    Server population and platform health are published on the status page. This host answers only to authorised machines.

    what a valid call looks like
    # heartbeat from an authorised game server
    curl -X POST https://api.roleplayproject.net/v1/health \
      -H "x-telemetry-key: <issued to our servers only>" \
      -H "Content-Type: application/json" \
      --data '{"version":"…","ips":["…"],"hostname":"…"}'
    
    # and what everyone else gets
    curl -i https://api.roleplayproject.net/v1/config
    HTTP/2 403
    curl -i https://api.roleplayproject.net/admin/servers
    HTTP/2 403   (from nginx, never proxied)
    curl -i https://api.roleplayproject.net/anything
    HTTP/2 404   (from nginx, never proxied)

    Found a weakness? Tell us privately through a support ticket on the UCP marked "security", and give us a reasonable window to fix it before talking about it publicly. We do not pursue good-faith researchers.