Skip to content

Scores ​

POST /scores submits a score. One endpoint covers both a normal (fan-out) submit and a submit to a single targeted board — the difference is one field.

This is the reference for the endpoint. For a walkthrough, see the REST quick start.

Request ​

POST https://api.cheddaboards.com/scores
HeaderValue
Content-Typeapplication/json
X-Game-IDyour Game ID
X-API-Keyyour API key (anonymous / API-key submit)
X-Session-Tokenthe player's session (signed-in submit — send this instead of the API key)

Body ​

FieldTypeRequiredNotes
playerIdstringyesThe player's persistent ID (dev_<unixtime>_<random> for anonymous)
gameIdstringyesYour Game ID (also sent as the header)
scorenumberyesThe run's score
streaknumberyesThe run's streak (send 0 if your game has no streak concept)
nicknamestringnoPresence is meaning: included → renames the player; omitted → stored name untouched. See nicknames
playSessionTokenstringnoFrom /play-sessions/start; required only if the game has time validation on
scoreboardIdstringnoPresent → targeted submit to that one board; absent → fan-out. See targeted submits

Response ​

Success:

json
{"ok":true,"data":{"message":"🎉 New high score and streak! Score: 1234, Streak: 3"}}

Check the ok field for success — message is human-readable feedback for the player (it varies: "New high score", "Score submitted", etc.), not a structured value to parse. On failure:

json
{"ok":false,"error":"<reason>"}

Fan-out submits (the default) ​

A submit with no scoreboardId fans out: the score is recorded against the player's profile and applied to every one of the game's standard time-based boards — all-time, weekly, daily, and any custom fan-out boards. One call, every standard board updated. This is what you want for a normal "player finished a run" submit.

Only the player's best survives on each board — submitting a lower score never replaces a higher one, and score and streak are kept as independent maxima. See what's stored.

Targeted submits ​

Add a scoreboardId and the submit goes to that one board only — no fan-out. This is how you run per-level or per-category leaderboards:

json
{
  "playerId": "dev_1730000000_1a2b3c4d",
  "gameId": "my-game",
  "score": 1000,
  "streak": 5,
  "scoreboardId": "level-14"
}

Behavior specific to targeted submits:

  • One board, no fan-out. Writes only to the named board.
  • Counts plays, not aggregate bests. A targeted submit increments the player's play count but doesn't move their aggregate profile score/streak — send a plain submit as well if you want both.
  • Per-board throttle. The 2-second rate limit is keyed per board, so chaining several targeted submits in one run (e.g. a level board plus a shared runs board) is fine.
  • The board must exist and be targeted. Submitting a scoreboardId for a board that doesn't exist returns "Scoreboard '<id>' not found for this game." — the API never auto-creates a board. Create it in the console first (Board Type → Targeted).
  • Never send a fan-out board's ID. all-time, weekly, daily and custom fan-out boards are updated by a plain submit; naming one in scoreboardId is rejected. Omit the field instead.

Full treatment: Category boards.

Nicknames ​

nickname on a submit is optional, and presence is meaning: a submit that includes it renames the player to that value, while a submit that omits it leaves the stored name untouched. So don't send it on every submit — send it only when the player has just chosen a name. The one place it's the natural tool: a brand-new player's first submit, which creates their profile — include nickname there and the profile is born named; omit it and the profile is created with a server-generated name (Player_1248) that stays until renamed via the nickname endpoints.

When present, it's applied subject to the rule 3–16 characters, letters, digits, and underscores (A–Z a–z 0–9 _). A taken name isn't an error — it's auto-suffixed (Chedz → Chedz_1). A genuinely invalid name is rejected and the player keeps their existing name. See Errors → nickname.

Anti-cheat & time validation ​

If the game has time validation enabled, include a playSessionToken from /play-sessions/start so the backend can check the score against elapsed play time. Without validation enabled, the token is accepted but not checked.

A score that trips a cap or time check is rejected with a generic "rejected by game validation rules" — the specific reason goes to your dashboard's suspicion log, not the client, so cheaters can't probe your limits. See Anti-cheat.

Rate limit ​

One submit per player per board every 2 seconds, enforced server-side. It's always on and not configurable — it only blocks bot-speed submission, never legitimate play. Because it's keyed per board, back-to-back submits to different boards don't trip it.

Retry safety ​

Submits are safe to retry. The backend keeps per-player bests, so resending a score after a timeout can never lower a score or streak — no client-side dedupe needed. Play counts are also protected against quick duplicates: repeat submits from the same player within a few seconds count as one play, so an immediate retry is fully safe. Only well-spaced duplicates move the play count — avoid long-running blind retry loops if play counts matter to you.

See also: REST quick start · Scoreboards · Category boards · Errors · What's stored

Can't be taken away. Open source backend and SDKs.