Hosted games: players and saves
What this is
A game that is only a web page can use Arena54 as its backend. Two modules are open: Identity (a player agrees once and your game learns their display name) and Saves (a few named values per player). Leaderboards and achievements is the third. None of them carries anything of value. A ledger and exchange come later, and each will say so here when it answers.
1. Register the game
- In the dashboard, open Your games and register it. Tick Hosted, Identity and Saves.
- Under Where the game runs, list its addresses, for example
https://play.example.comandhttp://localhost:5173. - Create a publishable test key for it. It starts with
a54_pub_test_and may sit in your page.
2. A sample player
With a test key you get a sample player without anyone signing in.
const API = "https://developers.arena54.com/api/v1/play";
const KEY = "a54_pub_test_…";
const { data } = await (await fetch(API + "/test-token", {
method: "POST",
headers: { Authorization: "Bearer " + KEY },
})).json();
const player = data.token; // send this as X-Arena54-Player3. Save and load
const headers = { Authorization: "Bearer " + KEY, "X-Arena54-Player": player };
await fetch(API + "/saves/slot-1", { method: "PUT", headers, body: JSON.stringify({ level: 3, coins: 120 }) });
const save = await (await fetch(API + "/saves/slot-1", { headers })).json();
console.log(save.data.value); // { level: 3, coins: 120 }| Method | Path | Purpose |
|---|---|---|
| POST | /v1/play/test-token | A sample player. Test keys only |
| GET | /v1/play/me | Who the token is: id, name, when it expires |
| GET | /v1/play/saves | The player’s saved values: names and sizes |
| GET | /v1/play/saves/{key} | One value |
| PUT | /v1/play/saves/{key} | Write one value. Any JSON |
| DELETE | /v1/play/saves/{key} | Remove one value |
A save’s name is 1 to 64 letters, digits, dots, dashes or underscores. Test saves and live saves are kept apart.
4. Real players
With a live key, send the player to Arena54 to agree. They come back to your page with a token in the address’s fragment.
// Send them. `redirect` must be inside an address you registered.
location.href = "https://arena54.com/play/authorize?game=YOUR-GAME-SLUG"
+ "&redirect=" + encodeURIComponent(location.origin + "/")
+ "&state=" + crypto.randomUUID();
// When they return: https://your-game/#a54_token=a54p_…&state=…
const back = new URLSearchParams(location.hash.slice(1));
const player = back.get("a54_token");
history.replaceState(null, "", location.pathname); // take the token out of the address bar- The player sees your game’s name, what it will learn (a display name and an id only your game sees) and where they will be sent.
- A token lasts two hours. When it is refused, send the player through the same address again. They see the same screen and press the button once more.
- A player can disconnect your game at
arena54.com/play/connections. The next call answers401 player_revoked.
5. Leaderboards
Tick Leaderboards and achievements for the game, then list its boards in the dashboard, one per line: an id, a name, whether high or low wins, the largest score you accept, and who reports it.
high-score | High score | high | 1000000 | page
fastest-lap | Fastest lap (ms) | low | 600000 | server// Report a score. The player’s best is kept; a worse one changes nothing.
const r = await (await fetch(API + "/boards/high-score", { method: "POST", headers, body: JSON.stringify({ score: 4200 }) })).json();
console.log(r.data); // { improved: true, best: 4200, rank: 7 }
// Show the top ten. This works before anyone signs in; with a player token it also says where they stand.
const top = await (await fetch(API + "/boards/high-score?limit=10", { headers })).json();
console.log(top.data.top, top.data.me); // [{ rank, player, name, score, verified, at }], { best, rank }| Method | Path | Purpose |
|---|---|---|
| GET | /v1/play/boards | The game’s boards |
| GET | /v1/play/boards/{id} | The top scores, 10 unless you send limit (100 at most). No player needed |
| POST | /v1/play/boards/{id} | Report {"score": n}: a whole number from 0 to the board’s largest |
A score reported by a page can be forged, because the page runs on the player’s device. Each row says so: verified is true only when your own server reported it with a secret key. If a board matters, mark it server and it refuses a publishable key with 403 server_only. A game with no server can keep the damage small with a sensible largest score. A player who disconnects the game leaves its boards at once.
6. Achievements
List them in the dashboard the same way: an id, a name, a description, and who unlocks it.
first-win | First win | Win a round | pageawait fetch(API + "/achievements/first-win", { method: "POST", headers }); // { slug, name, new: true } the first time
const all = await (await fetch(API + "/achievements", { headers })).json();
console.log(all.data); // [{ slug, name, description, writer, unlocked_at }]Unlocking twice is the same as once. Both calls need a player token. Test and live are kept apart, as with saves.
Limits and refusals
| Code | Status | When |
|---|---|---|
| origin_not_allowed | 403 | The page’s address is not one the game registered |
| module_not_enabled | 403 | The module is not ticked for this game |
| module_off | 503 | Arena54 has switched the module off |
| invalid_player | 401 | No token, an expired or altered one, or one for another game or mode |
| player_revoked | 401 | The player disconnected the game |
| test_only | 403 | A sample player was asked for with a live key |
| server_only | 403 | The board or achievement takes writes only from a secret key |
| bad_score | 400 | The score is not a whole number from 0 to the board’s largest |
| too_large | 413 | The value is over your plan’s size for one save |
| too_many | 409 | The player already holds your plan’s number of saves. Replacing one still works |
| bad_key, bad_json | 400 | The save’s name or the body is not acceptable |
| rate_limited | 429 | Too many calls this minute, for the key or for one player |
How many saves a player may hold, how large each may be, and how many boards and achievements a game may have come from your plan. See pricing.
What a page can never do
A page runs on the player’s device, and a player can change what it sends. So a token reaches only its own player’s saves, and nothing here grants anything of value. Do not keep a balance or an unlock in a save and trust it: a player can write their own saves. The same holds for a score or an achievement a page reports. Currency and items will come through the ledger, where a page cannot mint by asking.