Scores

Your game's server tells us how well a player is doing, and their standing becomes part of their place on your leaderboard and in your tournaments.

What it is

One call with one number. Your game already knows who its best players are; this is how it says so, and it is the only part of a player's standing on your leaderboard that comes from inside the game.

The number means whatever you mean by it. Points, coins, waves survived, a lap time you have turned upside down so bigger is better. Nothing here interprets it, and no game's score is ever compared with another game's: your players are ranked against each other, and that ranking is what becomes a 1 to 100 score on the board. A game that hands out millions a round and a game that counts to nine both work, and changing how you score does not break anybody's standing as long as it keeps the order.

1

Report the score from your game's server

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

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

// Wherever your server knows the number: end of a match, end of a run.
await indie.submitScore(playerId, 12500);

playerId is the player's indie.fun account id: the id of the signed-in user the browser SDK gives your game, which your server can confirm with indie.verifyToken(token). Only a player's best is kept, so reporting every round is fine, a bad run never lowers what they achieved, and a retried request never double-counts.

Only your server can report a score

A score decides a player's place on your leaderboard, which can decide whitelist places on your raise and the prizes of your tournaments. Code running in a player's browser is code the player controls, and anybody can type a score of a billion into the console. So a score is accepted only with your App Secret, which lives on your server and never in a page.

The browser SDK's indie.submitScore(score) used to send one from the page. It now sends nothing and warns in the console; games still calling it keep working, they just do not score.

2

Keep more than one score, if your game has them

A game often counts several things: experience, gold, a tournament high score. Name each one with a type and each keeps its own best for every player:

await indie.submitScore(playerId, 1250, { type: 'xp' });
await indie.submitScore(playerId, 40, { type: 'gold' });
await indie.submitScore(playerId, 98000, { type: 'tournament_highscore' });

A type is a name you choose: lower case letters, digits, _ and -, at most 32 of them (XP is read as xp). A score with no type is your game's score, which is what every score was before types existed. A game can report up to 50 types. Once your server has reported a type, your leaderboard's settings offer it when you add a column.

Where the number shows up

On your game's leaderboard at indie.fun/leaderboard/your-game, as the Game score column, and in your game's tournaments. Each score type is a column you add yourself.

Game score
Where a player sits among everyone who has scored in your game, as a percentile. The best player of your game is at the top whatever the raw number is, and it is weighted toward the top of the ladder, so being one of the best is worth a lot and being average a little.
A score type
Add a Score type column to a leaderboard, name the type and give it a heading, and it ranks the same way: where a player sits among everyone with a score of that type. A board can have one column per type, weighted like any other.
Tournaments
A tournament ranks on the best score your server reports while it runs: your game's score, or the one type you pick for it, so a tournament high score never mixes with XP. See Tournaments.

Reference

Server

submitScore(playerId, score, { type? })Report a player's score. 0 to 1e15. Only their best is kept, per type. No type is your game's score.

HTTP

POST /api/player/scoreBody { userId, score, type? }, with X-App-Id and X-App-Secret. Refused without the secret.