Skip to main content

Reporting Match Results

Ratings only change when you tell GameFlow how a match ended. After each finished match, your backend reports the teams and who won; GameFlow scores it with the model, updates every player's μ and σ, and stores the result. Between matches you can read a player's current rating to show a rank.

Both calls go to the GameFlow API base URL and send your game's key in the X-Api-Key header. Your backend holds the key; the client never sees it. GameFlow resolves which model to use from the live matchmaker for the game and mode, so you never send a model id.

Precondition

Reporting works only when a live matchmaker for that game mode has a Skill Rating Model node. Without one, the report is rejected because no model is configured for the mode. See Using a Model with a Matchmaker.

Report a result

Send the teams and a parallel ranks array. Lower rank is better, so the winning team is rank 1. Give teams equal ranks to express a draw.

const res = await fetch(`${GAMEFLOW_API_URL}/skill-rating/matches:report`, {
method: "POST",
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({
game_id: gameId,
game_mode: "default", // resolves the model, like the ticket's mode
match_id: gameflowMatchId, // GameFlow's match id, links the result to analytics
external_match_id: yourOwnId, // your id for the match, for idempotency/audit
teams: [
{ players: [{ external_player_id: "p1", display_name: "Ana" }] },
{ players: [{ external_player_id: "p2", display_name: "Beto" }] },
],
ranks: [1, 2], // team 0 won, team 1 lost
}),
});

const { ratings } = await res.json();
FieldDescription
game_idYour GameFlow game id.
game_modeThe mode that was played. Resolves which model scores the match.
match_idOptional. GameFlow's own match id, the one your server received in GAMEFLOW_PAYLOAD. Links this result to the match's analytics. Ratings are recorded identically without it; only the link is lost. See Match Analytics.
external_match_idYour own match identifier. Stored with the result.
teamsThe teams, each a list of players by external_player_id (and optional display_name).
ranksOne entry per team, in the same order. Lower is better; equal ranks mean a draw.

The response returns the updated rating for every player:

FieldDescription
skill_model_id, season_idWhat the result was recorded against.
match_idThe rating record's own id. This is not the match_id you send in the request, which is GameFlow's match id.
ratings[]Per player: mu_before / sigma_before, mu_after / sigma_after, and ordinal_after (the conservative μ − 3σ).

A player with no prior rating starts from the model's initial μ / σ, so their first report both creates and updates their rating.

Read a player's rating

To show a rank before or after a match, resolve the player's current rating for the game and mode:

const res = await fetch(`${GAMEFLOW_API_URL}/skill-rating/player-rating:resolve`, {
method: "POST",
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({
game_id: gameId,
game_mode: "default",
external_player_id: "p1",
}),
});

const data = await res.json();
if (data.found) {
const { mu, sigma, ordinal } = data.rating; // ordinal = μ − 3σ, for display
}

found is false when no model is configured for that game and mode, which is a valid FIFO setup. Treat it as "no rank to show" and hide the widget, rather than an error.

Full example

The Colyseus multiplayer example implements this exact flow: its game server reports the finished 2v2 to matches:report with the winning team ranked 1, and the client shows each player's resolved rank. The game side stays model-agnostic and sends only the game id and mode.