Playbox Organizer API
Read data about your own games, leagues and matches from your script or server: who played and how they paid, tables, top scorers, goals, cards and highlights. Read-only, your data only.
Base URL: https://app.play-box.xyz/api/organizer/v1/
First request in 5 minutes
- Get a key. In the Playbox app open Settings → Organizer tools → API keys → Create key. Give the key a name and accept the terms. The key looks like
pbo_xxxxxxxx.long-secretand is shown only once. Store it in your server’s environment variables. - Check the connection.
curl -H "X-Api-Key: $PLAYBOX_KEY" https://app.play-box.xyz/api/organizer/v1/me/
A 200 response returns your username, the key name and the date the key is valid until.
- Fetch your games.
curl -H "X-Api-Key: $PLAYBOX_KEY" "https://app.play-box.xyz/api/organizer/v1/events/?from=2026-09-01&status=FINISHED"
Key
- The key is personal: it acts on your behalf and sees only your data.
- A key is valid for 365 days. You get a reminder 14 and 3 days before it expires (push, Telegram, email). After that the API answers
key_expired: create a new key and replace it on your server. - You can have up to 5 active keys. Revoke an unused or leaked key at any time, it stops working immediately.
- Send the key in the
X-Api-Key: pbo_...header (orAuthorization: Api-Key pbo_...). A key in the URL is not accepted, because it would end up in logs. - Server only. A request from a browser (with an
Originheader) gets403 browser_not_allowed: a key inside a web page is visible to every visitor. - The key does not open the app, and an app session does not open this API.
What counts as your data
- games you organize, created, receive money for or manage;
- leagues you created or admin, and all their games;
- games linked to a community you own or admin (including games created by a co-organizer).
Someone else’s game or league returns 404, the same as one that does not exist. Player contacts and photos are never returned.
Limits
60 requests per minute per key. A request is one call to any API URL with a valid key, including 404 and 400 responses. The window resets at the start of every minute.
| Response header | Meaning |
|---|---|
X-RateLimit-Limit | limit per minute |
X-RateLimit-Remaining | requests left in the current minute |
X-RateLimit-Reset | seconds until the window resets |
Retry-After | only on 429: seconds to wait before retrying |
Store the data on your side and fetch only what is new: recent games by from and matches of finished games. Re-downloading the whole history every time your site opens will hit the limit within seconds.
Errors
Every error has the same shape. Branch on code, write message to your log.
{"error": {"code": "invalid_parameter", "message": "from: Use YYYY-MM-DD.", "fields": {"from": "Use YYYY-MM-DD."}}}
| HTTP | code | What to do |
|---|---|---|
| 401 | not_authenticated | the key header is missing |
| 401 | invalid_key | the key is wrong or revoked, create a new one |
| 401 | key_expired | the key has expired, create a new one |
| 403 | browser_not_allowed | the request came from a browser, call the API from your server |
| 404 | not_found | the object does not exist or is not yours |
| 400 | invalid_parameter | a parameter is wrong, details in fields |
| 405 | method_not_allowed | the API is read-only, use GET |
| 429 | rate_limited | wait retry_after seconds |
Data format
- Times are always UTC, ISO 8601. Money comes as a pair:
{"amount": 800.0, "currency": "RSD"}. - Lists (
leagues/,events/) are paginated:?limit=50&offset=0,limitup to 100. The response is{"count", "limit", "offset", "next_offset", "results"}, andnext_offset: nullmeans the last page. - Player:
{"id", "username", "is_guest", "url"}.is_guest: trueis the shared guest account used for goals by players without a profile: they count in the match score, not in personal stats. - Links. Games, matches, leagues and players have a
urlto their Playbox page. Use it when you show the data on your site: the page has full stats and video.
Endpoints
GET /me/
Whose key this is: user, key name, expiry date, terms version, limits.
GET /leagues/
Your leagues: id, slug, name, status (active, finished, archived), format, city, date_start, date_end, teams_count, price, game_price, url.
GET /leagues/{id}/
The league and its rules: technical defeat, discipline (yellow card limit, suspensions), squad size.
GET /leagues/{id}/table/
The standings in the same order as in the app: position, team, played, wins, draws, losses, goals_for, goals_against, goal_difference, points.
GET /leagues/{id}/players/
Player table: player, team, position, games, goals, assists, yellow_cards, red_cards, suspension_games_left. Sorted by goals, then assists.
GET /leagues/{id}/rosters/
Team squads: each team with its players and the date they were added.
GET /events/
Your games, newest first. Filters: from, to (YYYY-MM-DD by start date, UTC), status (NOT_STARTED, STARTED, FINISHED, CANCELLED), league (id), format (players per team, for example 8). Fields: id, league_id, starts_at, ends_at, status, results_status, format, venue, is_paid, price, price_per_player, capacity, players_count, teams_count, is_ranked, is_private, url.
GET /events/{id}/
The whole game: teams (colour, wins, draws, losses, squad), matches with score and url, mvp_player_id, YouTube recordings, live streams.
GET /events/{id}/participants/
Who signed up and how they paid: player, status (In game, Late, Did not come, Invited), team_id, joined_at, payment_method (CASH, WALLET, CARD, STRIPE, APPLE_PAY, GOOGLE_PAY, TELEGRAM), paid, payment_confirmed, paid_by, amount, is_early_bird. This data is for you only: do not publish it.
GET /events/{id}/player-stats/
Each player’s result for the game: wins, draws, losses, goals, assists, goals_conceded, yellow_cards, red_cards, rating, rating_change, is_confirmed. Available after the game is finished.
GET /matches/{id}/
The whole match: event_id, league_id, number, tour, starts_at, ends_at, state, teams, score, technical_defeat, referee, video_url, url, the timeline, the penalty_shootout, lineups (who started, seconds on the pitch) and highlights (as YouTube links).
"timeline": [
{"type": "period", "half": 1, "is_extra_time": false, "started_at": "...", "ended_at": "...", "duration_seconds": 1200},
{"type": "goal", "game_time_seconds": 305, "team_id": 1, "player": {}, "assistant": {}, "is_own_goal": false, "youtube_url": "..."},
{"type": "card", "game_time_seconds": 700, "team_id": 2, "player": {}, "card": "YELLOW", "reason": "Dissent"},
{"type": "substitution", "game_time_seconds": 900, "team_id": 2, "player_out": {}, "player_in": {}, "until_end": true}
]
- Event types:
period,goal,card,substitution,player_added,player_removed. Halves 3 and 4 are extra time. - An own goal (
is_own_goal: true) is stored under the team it counts for. - On a technical defeat the
scoreis the awarded one, the reason and score are intechnical_defeat.
Examples
Python
import os, requests
API = "https://app.play-box.xyz/api/organizer/v1"
session = requests.Session()
session.headers["X-Api-Key"] = os.environ["PLAYBOX_KEY"]
def fetch_all(path, **params):
offset = 0
while offset is not None:
page = session.get(f"{API}/{path}", params={**params, "offset": offset, "limit": 100}).json()
yield from page["results"]
offset = page["next_offset"]
for event in fetch_all("events/", status="FINISHED", **{"from": "2026-09-01"}):
detail = session.get(f"{API}/events/{event['id']}/").json()
for match in detail["matches"]:
print(event["starts_at"], match["score"], match["url"])
JavaScript (Node 18+, server side)
const API = "https://app.play-box.xyz/api/organizer/v1";
const headers = { "X-Api-Key": process.env.PLAYBOX_KEY };
const res = await fetch(`${API}/leagues/8/players/`, { headers });
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { results } = await res.json();
console.log(results.slice(0, 10).map(r => `${r.player.username}: ${r.goals}`));
Terms of use (version 2026-10-01)
- Use API data only to run your own games, leagues and community.
- Do not publish payments, amounts, payment methods, attendance statuses or player photos. Sports stats (scores, goals, assists, tables) can be shown publicly.
- Do not share or sell the data to third parties.
- Do not take bookings or payments for games outside Playbox.
- When showing the data publicly, credit the source as “Data by Playbox” with a link to Playbox.
- Keep the key on your server and never share it. If it leaks, revoke it right away.
- Breaking these terms gets the key revoked. After revocation, delete any stored personal data of players.
Versions
The version is in the URL (v1). New fields may appear in responses without a version change, so do not fail on unknown fields. Removing or renaming a field will happen only in v2, and v1 will keep working.
- 2026-10-02, v1. First release: leagues, games, participants, matches, links to Playbox, key lifetime, terms, rate limit headers, single error format.