Log player events
Log things your players did in the game, up to 500 events in one call, to be shown on member profiles and rewarded.
POST /v1/games/{game}/player-events · requires players:write
Call it from your server when players do something worth knowing: win a match, enter the cup, finish a level. Each event is added to the member history of the member the player is connected to, where it shows on their profile and counts in segments. An event can also earn the reward the community set for it. Player data explains what an event is and how a retry stays safe.
Path parameters
Prop
Type
Body parameters
Prop
Type
Each entry of events holds:
Prop
Type
Returns
Prop
Type
A batch with problems still returns 200.
status of an event | What happened |
|---|---|
logged | The event is in the member history |
duplicate | This event ID was logged before. Nothing was added |
held | The player has not connected, and the game holds events for players who have not. The event is held until the player connects |
held_limit | The held store is full, or the player already has 50 held events |
not_connected | No active connection exists for this player |
not_member | The player is connected, but the member is not in the server. Events are stored for members only |
not_consented | The member has not consented to sharing the activity category: they connected before the game shared it and have not consented since on their connections page. A connection your own server created carries no consent and is not checked |
erased | The player was erased, and is never held |
under_age | You sent underAge: true. Nothing is stored |
not_approved | The event is not approved in the field map. It is now proposed. Send it again after approval, within 7 days |
unknown_event | The game already has 50 proposed events, so the name was not added |
too_old | occurredAt is more than 7 days back |
in_future | occurredAt is more than 5 minutes ahead |
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. reward is queued when the event earns a reward that CEEJAY's bot grants asynchronously, and none otherwise.
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/games/rugby_manager/player-events \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"eventId": "m-88213",
"playerId": "p_19",
"event": "match_won",
"occurredAt": "2026-10-07T19:02:00Z",
"quantity": 1
},
{
"eventId": "m-88214",
"playerId": "p_77",
"event": "match_won",
"occurredAt": "2026-10-07T19:05:00Z"
}
],
"dryRun": false
}'The TypeScript tab calls ceejay.players.logEvents from the TypeScript SDK. It returns the data of the response below.
{
"success": true,
"data": {
"results": [
{ "index": 0, "eventId": "m-88213", "status": "logged", "reward": "queued" },
{ "index": 1, "eventId": "m-88214", "status": "not_connected", "reward": "none" }
],
"summary": {
"logged": 1,
"duplicate": 0,
"held": 0,
"refused": 1,
"rewardsQueued": 1
}
}
}What an event is
An event is one row of the member history: app Account Linking, the action "Game event", and the event's label with the game's name. It shows on the member's profile, and a segment can filter on members who did something in a game, with a count and a number of days. It does not trigger a webhook: a subscription to all events would receive each one, up to 500 in one call. An automation can be triggered by it when it is set up for "Game event", and only for a verified connection unless the automation also includes unverified connections.
- Retries are safe because
eventIdis idempotent. Send the sameeventIdagain and the status isduplicate. Use an ID that names the thing that happened, such as a match number. - An event name has to be approved in the field map before it is stored. An unknown name is proposed and gets the status
not_approved. Send it again after approval, within 7 days of when it happened. - Events take no free text. A quantity is stored only where the event's entry keeps one, and is off by default. Send money and other private values as fields, not as events.
- Members only. A connected player who is not in the server gets the status
not_member. - Under age.
underAge: trueon an event needs the key to holdplayers:connectas well. Without it the whole call returns403with the codeinsufficient_scopeandrequired: ["players:connect"]. A call can carry at most 50 such events, or it returns400with the codebad_request.
Rewards
An event that has a reward list returns reward: "queued", and CEEJAY's bot grants the reward asynchronously: this call does not wait for it. A reward is queued only when the event is less than 24 hours old, the connection meets the verification level the game requires, and the member is in the server. For a reward that is granted once to each member, a member who was already granted the reward for that event is not queued again. See Game rewards.
What the call needs
- The member history has to be enabled on this instance. Where it is off, or its table is missing, the call returns
404with the codenot_availableandfeature: "member_history". - The
activitycategory has to be one the game shares. Otherwise the whole call returns409with the codeactivity_not_shared.
Limits
In addition to the key's own requests per minute, a game has a limit of events a minute: 3,000 unless another number was set for the game. Above it the whole call returns 429 with the code event_rate_exceeded, retryAfter and a Retry-After header, and stores nothing. A call that is over the limit counts too, so one that keeps going stays rate limited until the window resets.
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. Send fewer events in one call.
Safe to repeat
The eventId of each event is the key, 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 an event 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 events is one request.
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 | events must be a list of 1 to 500 events | events is missing, is not a list, or is empty |
400 | too_many_events | A call can carry up to 500 events | events contains more than 500 events |
400 | bad_request | A call can carry up to 50 events with underAge: true | More than 50 entries of events 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 | not_available | This is not available on this instance yet. | The member history is disabled on this instance, or its table is missing, and events are stored in it. The body adds feature, which is member_history |
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 |
409 | activity_not_shared | This game does not share the activity category, so events cannot be logged. The team adds it to what the game shares first | The game does not share the activity category. The community's team adds it to what the game shares first |
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 |
429 | event_rate_exceeded | This game has sent more events this minute than it is allowed. Wait and send them again | The game sent more events this minute than it is allowed (3,000 unless another number was set for the game). The body adds retryAfter, and the Retry-After header carries the same number |
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.

