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

  1. In the dashboard, open Your games and register it. Tick Hosted, Identity and Saves.
  2. Under Where the game runs, list its addresses, for example https://play.example.com and http://localhost:5173.
  3. 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.

JavaScript, in your page
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-Player

3. Save and load

JavaScript
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 }
MethodPathPurpose
POST/v1/play/test-tokenA sample player. Test keys only
GET/v1/play/meWho the token is: id, name, when it expires
GET/v1/play/savesThe 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.

JavaScript
// 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 answers 401 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.

In the dashboard
high-score | High score | high | 1000000 | page
fastest-lap | Fastest lap (ms) | low | 600000 | server
JavaScript
// 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 }
MethodPathPurpose
GET/v1/play/boardsThe 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.

In the dashboard
first-win | First win | Win a round | page
JavaScript
await 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

CodeStatusWhen
origin_not_allowed403The page’s address is not one the game registered
module_not_enabled403The module is not ticked for this game
module_off503Arena54 has switched the module off
invalid_player401No token, an expired or altered one, or one for another game or mode
player_revoked401The player disconnected the game
test_only403A sample player was asked for with a live key
server_only403The board or achievement takes writes only from a secret key
bad_score400The score is not a whole number from 0 to the board’s largest
too_large413The value is over your plan’s size for one save
too_many409The player already holds your plan’s number of saves. Replacing one still works
bad_key, bad_json400The save’s name or the body is not acceptable
rate_limited429Too 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.