Embed Your Server Stats
Display your PlayerAnalytics server's live stats — players, leaderboards, who's online — anywhere on your own website, using a simple read-only key.
PlayerAnalytics gives every server owner a Stats Embed Key: a read-only key your web developer can use to pull your server's public stats and show them on your website, Discord widget, or anywhere else.
It returns the same stats a player sees on your public server page — player counts, leaderboards, play-style breakdowns, who's online. Private admin metrics are never exposed. This guide is written for the developer doing the integration.
What you'll need
Ask the server owner for two things, both found in their dashboard under Server Settings → Stats Embed Key:
- The Stats Embed Key — a value beginning with
pas_. It's shown once when generated; the owner copies it to you. - The endpoint URL — shown right beside the key in the dashboard. It looks like
https://api.playeranalytics.org/api/servers/<id>/statsand already contains the server's ID.
That's it — no account, OAuth, or SDK required. Authentication is a single HTTP header.
How it works
- Your back-end makes a
GETrequest to the stats endpoint, sending the key in anX-API-Keyheader. - You cache the response briefly (the data updates about once a minute).
- Your front-end reads the cached data from your server and renders it — no key ever reaches the browser.
Authentication
Send the key as an X-API-Key request header on every call:
# Quick test from your terminal
curl -H "X-API-Key: pas_your_key_here" \
https://api.playeranalytics.org/api/servers/<id>/stats
Available data
All endpoints are GET requests under https://api.playeranalytics.org/api/servers/<id>/ using the same header:
| Endpoint | Returns |
|---|---|
| /stats | Headline numbers — players tracked, sessions, kills, deaths, average session length, play-style mix |
| /players | Player roster & leaderboard (paginated) |
| /playtime | Most-played leaderboard |
| /online | Who's online right now |
| /progression | SkillTree levels & Mercenary ranks |
| /marksmanship | Headshot & long-shot leaderboards |
Paging
The list endpoints (/players, /playtime, /marksmanship) are paginated and accept two optional query parameters:
| Parameter | Meaning |
|---|---|
page | 1-based page number. Past the last page you get an empty list, not an error. |
limit | Rows per page. |
Every paginated response carries total, page and pages, so you can render a pager without a second call:
curl -H "X-API-Key: pas_your_key_here" \
"https://api.playeranalytics.org/api/servers/<id>/players?page=2&limit=25"
Example response — /stats
There is more here than most integrations use. Alongside the headline counts you get a play-style breakdown of your player base, a top-weapons table, and a full activity histogram — everything needed to build a real stats page rather than a player counter.
{
"server_name": "Your Server",
"stats": {
"total_players": 1240,
"total_sessions": 8300,
"total_deaths": 5100,
"pvp_deaths": 2600,
"npc_deaths": 2500,
"avg_session_min": 64.2,
"current_fps": 58,
// how your tracked players actually play
"playstyle_raider": 62,
"playstyle_builder": 8,
"playstyle_farmer": 7,
"playstyle_crafter": 0,
"playstyle_grower": 0,
"playstyle_allround": 0
},
"deaths": { "total": 5100, "pvp": 2600, "npc": 2500, "rage": 41 },
"npc_kills_total": 7896,
// shortnames, not display names — see the note below
"top_weapons": [
{ "weapon": "m249.entity", "kills": 2262 },
{ "weapon": "lr300.entity", "kills": 1057 }
],
// ~37 event types with counts: loot, build, gather, craft, …
"activity": [
{ "event_type": "loot", "count": 25109 },
{ "event_type": "build", "count": 7325 }
]
}
top_weapons[].weapon comes straight from the game: m249.entity,
axe_salvaged.entity, bone.knife.obsidian.entity. Strip the
.entity suffix and map to display names yourself — note that Rust puts the
qualifier last, so axe_salvaged reads as "Salvaged Axe".
Example: a small back-end proxy
The cleanest setup is a tiny endpoint on your own server that holds the key, calls PlayerAnalytics, caches the result, and returns it to your front-end. Here's a Cloudflare Worker version — the same idea works in Node/Express, a Next.js API route, PHP, Python, etc.
// The key lives in a Worker secret, never in client code:
// wrangler secret put PA_KEY
export default {
async fetch(req, env) {
const r = await fetch(
"https://api.playeranalytics.org/api/servers/<id>/stats",
{ headers: { "X-API-Key": env.PA_KEY }, cf: { cacheTtl: 60 } }
);
return new Response(await r.text(), {
status: r.status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // your own site can read it
"Cache-Control": "public, max-age=60"
}
});
}
};
Node / Express equivalent:
app.get("/server-stats", async (req, res) => {
const r = await fetch(
"https://api.playeranalytics.org/api/servers/<id>/stats",
{ headers: { "X-API-Key": process.env.PA_KEY } }
);
res.set("Cache-Control", "public, max-age=60");
res.status(r.status).json(await r.json());
});
Your page then fetches from your own route (same origin, no key exposed):
const data = await fetch("/server-stats").then(r => r.json());
document.querySelector("#players").textContent = data.stats.total_players;
Caching and rate limits
The underlying data refreshes roughly once a minute, so caching each response for 30–60 seconds is plenty. It keeps your site fast and avoids unnecessary requests.
Each key is limited to 300 requests per minute. That is far above what a cached integration needs — at 60-second caching, six endpoints cost six requests a minute no matter how much traffic your site gets. The limit exists so that a key which leaks (pasted into a Discord message, committed to a public repo) can't be used to hammer the API.
Exceeding it returns 429 with a Retry-After: 60 header. If you see one, you are almost certainly calling per page-view instead of from a cache — add the cache before raising anything with us.
Response codes
| Code | Meaning |
|---|---|
| 200 | Success — JSON body as shown above. |
| 401 | Missing or invalid key. If it worked before, the owner may have rotated it — ask for the new key. |
| 402 | The server's PlayerAnalytics subscription is inactive. Handle this gracefully (e.g. hide the widget) until it's active again. |
| 403 | That data isn't available with an embed key (it only exposes public, player-facing stats). |
| 429 | Rate limit exceeded (300/min per key). Honour Retry-After and cache your responses. |
Rotating or disabling a key
The owner controls the key from Server Settings → Stats Embed Key. They can Rotate it (the old key stops working immediately — update your stored value) or Disable it entirely. Each server has its own key; a key only works for that one server.