Set player fields
Send values about your players, up to 500 players in one call, to be stored on the members they are connected to.
POST /v1/games/{game}/player-fields · requires players:write
Call it from your server whenever values about players change: a league position, a club, the day a player last played. The values are stored on the member the player is connected to, shown on their profile, and usable in segments and filters. Player data explains how the values are checked and what a result means, and the field map how a name your game uses becomes a field.
Path parameters
Prop
Type
Body parameters
Prop
Type
Each entry of players holds:
Prop
Type
Returns
Prop
Type
A batch with problems still returns 200. Only a body that is not shaped right is refused as a whole.
status of a player | What happened |
|---|---|
written | At least one value was new or changed. The previous value is retained in the history |
unchanged | Every value was already the current one, or there was nothing to clear |
held | The player has not connected, and the game holds data for players who have not. The values are held until the player connects |
held_limit | The held store is full for this game (50,000 players) |
not_connected | No active connection exists for this player |
not_member | The player is connected, but the member is not in the server. Values are stored for members only |
erased | The player was erased, and is never held |
under_age | You sent underAge: true. Nothing is stored |
bad_item | The entry could not be read: message says why. The rest of the batch was processed |
This list may grow: handle unknown values. An entry that names the player by discordId gets discordId only in its result, and one that names the player by playerId gets playerId only.
Each item of problems has field, the name you sent, and a code:
code of a problem | The field |
|---|---|
unknown_field | The name is new and the game already has 50 proposed fields, so it was not added |
not_approved | The field is proposed and waiting for approval by the community's team, or the name was new and is now proposed |
unused | The team marked the field unused, so nothing is written |
bad_value | The value does not fit the field's type, or is out of range |
not_a_choice | The value is not one of the choices of a dropdown field. It is offered to the team |
not_consented | The member has not consented to the field's category |
needs_verified_proof | The player is connected at the basic level, and the field is in the purchases or location category: it is not stored for a connection whose player ID the game supplied and CEEJAY did not verify |
managed | The field is filled from the connection itself (the player ID and name) and cannot be set |
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/games/rugby_manager/player-fields \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"players": [
{
"playerId": "p_19",
"fields": {
"league_position": 3,
"club": "Harbour Town",
"last_played": "2026-10-07T21:10:00+02:00",
"coach_notes": "Ready for the cup"
}
},
{
"playerId": "p_77",
"fields": { "league_position": 9 }
}
],
"dryRun": false
}'The TypeScript tab calls ceejay.players.setFields from the TypeScript SDK. It returns the data of the response below.
{
"success": true,
"data": {
"results": [
{
"index": 0,
"playerId": "p_19",
"status": "written",
"written": 2,
"unchanged": 1,
"cleared": 0,
"problems": [{ "field": "coach_notes", "code": "not_approved" }]
},
{
"index": 1,
"playerId": "p_77",
"status": "not_connected",
"written": 0,
"unchanged": 0,
"cleared": 0,
"problems": []
}
],
"summary": {
"players": 2,
"written": 2,
"unchanged": 1,
"cleared": 0,
"held": 0,
"refused": 1
}
}
}How values are checked
- Set, not added. A value equal to the current one is not a new history entry.
nullclears a field. A field that is not in the object is left as it is. - The name. It is matched to the name your game uses for the field first (the
sourceKeyof the field map), then to the field's key, case-insensitively. A name nothing matches is added to the field map as a proposal, up to 50 at a time, and returnsnot_approved. Seven standard fields that need no choices are approved by the first value. All other fields need approval from the community's team. - Numbers beyond 1e15 either way are refused. A number is multiplied by the field's scale and rounded to 6 places: a scale of
0.01turns cents into dollars. - Dates are
YYYY-MM-DD, or a full time with an offset, which becomes its UTC day. Anything else isbad_value. - Dropdowns pass your value through the field's value map, and it must then be one of the choices. A value that is not is offered to the team and returns
not_a_choice. - Text is up to 500 characters. An object or a list is
bad_value. - Sharing. A connection a member made through Connect stores the categories the member consented to, and a field of another category is
not_consented. A connection your own server created stores none, and your server is responsible for the consent. - Verification. A player connected at the basic level, where the game supplied an unverified player ID, does not accept fields of the
purchasesorlocationcategory: each is a problem with the codeneeds_verified_proof, and the player's other fields are still written.progressfields, and players your own server linked, are not affected. - Under age.
underAge: trueon a player needs the key to holdplayers:connectas well. Without it the whole call returns403with the codeinsufficient_scopeandrequired: ["players:connect"], and nothing is written. A call can carry at most 50 such players, or it returns400with the codebad_request.
Values are stored for members only. Nothing here creates a member profile.
Body size
The body is read before it is parsed, up to 512 KB. A larger one is refused with 413 and the code body_too_large, in the usual error shape, and nothing is processed. Send fewer players in one call.
Safe to repeat
Sending the same values again changes nothing, so the request takes no idempotencyKey. The SDK does not retry automatically; retrying it yourself is safe.
The scope
players:write is part of *, so a Full Access key can call this. A call that says underAge: true for a player also needs players:connect, which * does not include.
Limits
This request is counted against the key's own requests per minute: 60 unless a higher limit was set on it, and never more than 600. Rate limits has the numbers and the headers.
A batch of 500 players is one request, so 60 calls a minute is 30,000 players.
Errors
| Status | code | error | When |
|---|---|---|---|
400 | bad_request | The request body must be a JSON object | The body is null, text, a number or a list |
400 | bad_request | The request body must be JSON | The body is not valid JSON, or could not be read |
400 | bad_request | players must be a list of 1 to 500 players | players is missing, is not a list, or is empty |
400 | too_many_players | A call can carry up to 500 players | players contains more than 500 players |
400 | bad_request | A call can carry up to 50 players with underAge: true | More than 50 entries of players say underAge: true |
400 | bad_request | dryRun must be true or false | dryRun was sent and is not true or false |
403 | community_not_active | This community is no longer active on CEEJAY | The community removed CEEJAY from its server |
403 | game_not_allowed | This API key is limited to other games | The key is limited to other games of the community |
403 | audience_not_allowed | A game made for children cannot connect players or send player data | The game is set up as made for children. Such a game connects nobody and takes no player data |
404 | not_available | This is not available on this instance yet. | Connect is not enabled for this instance. It is enabled per instance. The body adds feature, which is players. Retrying will not change the response |
404 | game_not_found | Game not found | The community has no game with this slug, or the game was removed. A slug that is not 2 to 32 lower-case letters, digits and underscores, starting with a letter, gets this response too |
409 | game_paused | This game is paused | The game is paused or suspended |
413 | body_too_large | The request body is larger than 512 KB. Send fewer players or events in one call | The body is larger than 512 KB. It is refused before it is parsed |
Every error body also carries requestId, and a 400 with the code bad_request names the invalid field in field. Match on code, not on the message text, which can change.
The errors every request shares (401, 403 for a missing scope, 405, 429, 500) are on the Errors page.

