Plays

Your game's server tells us each time a player plays a match or a run: how long it lasted and how many points it earned. Your leaderboard ranks players on those points, and anybody can open a player's plays from the board.

What it is

A play is whatever your game counts as one: a match, a run, a round, a race. When one ends, your server reports it with three things: your own id for it, how many seconds it lasted, and the points the player earned in it. You decide the points. A kill can be worth 10 and a win 100, or every finished run can be worth 1; nothing here interprets the number, and your players are only ever ranked against each other.

Three things on your leaderboard come from players playing, and they answer different questions:

Game score
How good a player is: the best single score your server has reported for them, ever. See Scores.
Play points
How much a player has played and achieved lately: the points of every play your server reported for them in the last 90 days, added up. This page.
Attendance
How often a player comes back: days in the last 90 they played for at least a few minutes. You report nothing for it; the SDK measures it once it is in your game (see Analytics).

You can report a score and a play from the same place at the end of a match. They are separate calls because they are kept differently: a score keeps only the best, a play is kept one by one.

1

Know who is playing

A play belongs to an indie.fun account. In the browser, the SDK tells your game who is signed in; send their token to your server with the rest of your game's traffic.

// In the browser
const indie = new Indie({ appId: 'YOUR_APP_ID' });

indie.onChange(({ user, token }) => {
  // user is null until somebody signs in
  if (user) socket.emit('hello', { token });
});

On your server, turn the token into the account id once, when the player connects, and keep it with the player:

// On your server
import { Indie } from 'indie-sdk/server';

const indie = new Indie({
  appId: process.env.INDIE_APP_ID,
  appSecret: process.env.INDIE_APP_SECRET,
});

const player = await indie.verifyToken(token); // { userId, name } or null
if (player) connection.indieUserId = player.userId;
2

Report the play when it ends

// Wherever your server decides a match or a run is over
await indie.recordPlay(connection.indieUserId, {
  playId: match.id,                   // your own id for this match or run
  durationSec: match.durationSeconds, // how long this player played it
  points: playerPointsFor(match),     // whatever your game gives for it
});

playId is required, and it is what makes retrying safe: a play is recorded once, so sending the same id again changes nothing. Use something your game already has, such as the match id: it only has to be unique for each player, so everybody who played one match can share it.

endedAt is optional (epoch milliseconds or a Date) and defaults to now, so a play you report as it finishes needs nothing. Use it for plays you send later, up to 90 days back. recordPlay resolves true once indie.fun has the play, and false if it was refused or never arrived, so you can queue it and try again.

points
A number from 0 to 1,000,000,000. Zero is fine: a play that earned nothing is still a play.
durationSec
Seconds, from 0 to 86,400 (a day). Rounded to whole seconds.
playId
Up to 128 characters, unique for each player in your game.

Only your server can report a play

Play points decide a player's place on your leaderboard, which can decide whitelist places on your raise and the size of tournament prizes. Code in a player's browser is code the player controls, so a play is accepted only with your App Secret, which lives on your server and never in a page. The browser SDK has no way to send one.

3

Rank your players on it

In your game's Leaderboard section, open a board, press Add column and pick Play points. Like every column it is a percentile of your own players, weighted against the others (see Leaderboard), so a game that gives a thousand points a match and one that gives three are ranked the same way. Plays older than 90 days stop counting. The board updates within a minute of a play arriving.

On the board, pressing a player's name opens their profile, which lists their plays: the day each was played, how long it lasted and the points it earned. Plays are shown by day, never by time of day, because the board is public.

4

Check it worked

Check it worked

Send one play by hand with your App ID and App Secret, using your own account id:

curl -X POST https://indie.fun/api/player/play \
  -H "Content-Type: application/json" \
  -H "X-App-Id: YOUR_APP_ID" \
  -H "X-App-Secret: YOUR_APP_SECRET" \
  -d '{"userId":"YOUR_ACCOUNT_ID","playId":"test-1","durationSec":120,"points":5}'

It answers {"ok":true}. Send it again and it answers {"ok":true,"duplicate":true}: the play was kept once. Then open your board at indie.fun/leaderboard/your-game, join it if you have not, and press your name: the play is there.

A 401 means the request did not carry a valid App Secret; a 400 says which field was wrong.

Reference

Server

recordPlay(playerId, { playId, durationSec, points, endedAt? })Report one play. Recorded once per playId. Resolves true once indie.fun holds it.

HTTP

POST /api/player/playBody { userId, playId, durationSec, points, endedAt? }, with X-App-Id and X-App-Secret. Refused without the secret. A playId already recorded answers { ok: true, duplicate: true }.