Get the result
Read the status of a connect session. When it is approved, the response includes the member summary, a player session and a refresh token.
POST /v1/connect/result · for public key
The game gets the result by polling. Poll every 3 seconds while a session is open, and immediately when the player returns to the game.
Body parameters
Prop
Type
Returns
The response has one of four shapes, distinguished by status.
While the session is waiting for the member (pending, needs_confirmation):
Prop
Type
While the session is waiting for the game to confirm (needs_game_confirmation):
Prop
Type
When the session ended without a connection (declined, cancelled, expired, locked, refused):
Prop
Type
When both sides approved (approved):
Prop
Type
member is the member summary. A game receives only these fields about the member:
Prop
Type
connection holds:
Prop
Type
sharing holds agreed, the kinds of data the member has consented to share with the community's team (progress, activity, purchases, location), and needed, the kinds the game has added since. A kind in needed is not shared for this member until they give consent at manageUrl.
reward holds status and items. CEEJAY's bot grants the reward for connecting shortly after the member approves, so a new connection reads pending at first: poll again in a few seconds. items lists what was granted once status is paid. The other values say why nothing was granted: none (the game has no reward for connecting, or the session ended without a reward), already_paid (this member or this player was rewarded before), needs_verified_proof (the game rewards only verified connections), not_member (the member is not in the server) and waiting_screening (the member has not passed Discord's membership screening; CEEJAY checks again every 15 minutes for 7 days). See Game rewards.
reason | Why the session ended |
|---|---|
member_cancelled | The member pressed Cancel, or kept the player they had |
game_declined | The game declined |
banned | The Discord account is banned from the server |
player_blocked | The community blocked the player |
player_already_connected | The player is connected to another Discord account, and could not be moved |
expired | 10 minutes passed |
too_many_tries | The code was entered incorrectly five times |
under_age | The player is under age |
null | A newer session for the same player superseded it, or the member connected another player in the meantime |
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/connect/result \
-H "Authorization: Bearer $CEEJAY_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionSecret": "ceejay_cs_4f8a2c1e9b7d3e41f8a6b0c1d25f0c2a9b7d3e41f8a6b0c1d24f8a2c1e9b7d3e"
}'The TypeScript tab calls connect.getResult of the Connect client in the TypeScript SDK. Leave the session out to read the one connect.start opened. To wait until it ends, call connect.waitForResult.
{
"success": true,
"data": {
"status": "pending",
"expiresAt": "2026-10-08T18:14:11.000Z"
}
}{
"success": true,
"data": {
"status": "needs_game_confirmation",
"expiresAt": "2026-10-08T18:14:11.000Z",
"member": {
"displayName": "Nova",
"avatarUrl": "https://cdn.discordapp.com/avatars/409876543210987654/a1b2c3d4e5f60718293a4b5c6d7e8f90.png"
},
"alreadyConnected": false
}
}{
"success": true,
"data": {
"status": "declined",
"reason": "member_cancelled"
}
}{
"success": true,
"data": {
"status": "approved",
"connection": {
"connectionId": "8841",
"game": "rugby_manager",
"playerId": "p_19",
"proof": "verified",
"connectedAt": "2026-10-08T18:06:40.000Z",
"moved": false,
"alreadyConnected": false,
"spendHoldUntil": "2026-10-09T18:06:40.000Z"
},
"member": {
"displayName": "Nova",
"nickname": "Nova | EU",
"avatarUrl": "https://cdn.discordapp.com/avatars/409876543210987654/a1b2c3d4e5f60718293a4b5c6d7e8f90.png",
"level": 12,
"xp": 5420,
"coins": 1450,
"roles": [
{
"id": "982371004827100394",
"name": "Tournament winner"
}
],
"membership": "member",
"joinUrl": null
},
"join": {
"asked": false,
"result": "not_asked",
"failure": null
},
"sharing": {
"agreed": [
"progress",
"activity"
],
"needed": []
},
"reward": {
"status": "pending",
"items": []
},
"manageUrl": "https://ceejay.communitystudios.xyz/connect/mine",
"playerSession": {
"token": "cjps1.eyJ2IjoxLCJhdWQiOiJnYW1lIiwicyI6IjcyMTEwNDkzODI3MTAwMzg0MiIsImciOjEyfQ.pV3mQ0b6k1y8ZtL2c9XwR4s7Nf5aHj0uEoGdBiKq3M",
"expiresAt": "2026-10-08T18:21:44.000Z"
},
"refreshToken": "ceejay_rt_5512.1.4f8a2c1e9b7d3e41f8a6b0c1d25f0c2a9b7d3e41f8a6b0c1d24f8a2c1e9b7d3e",
"refreshExpiresAt": "2027-01-06T18:06:44.000Z"
}
}What to do at each status
status | The game |
|---|---|
pending, needs_confirmation | Shows that it is waiting, and polls again in 3 seconds |
needs_game_confirmation | Shows "Connect to" and the Discord name with options to confirm or cancel, and sends Confirm connect. There is no default: the player decides |
approved | Stores the refresh token, keeps the player session, discards the session secret |
declined, cancelled, expired, locked, refused | Discards the session and returns to not connected |
Reading an approved result again
The result of an approved session can be read for 10 minutes. Each read returns a new refresh token and invalidates the one returned before, so a lost response can be requested again and still leaves one device signed in: keep the last one.
After those 10 minutes the response is 410 with the code result_gone. The same response is returned when what the session approved has changed since: the member disconnected, moved to another player, or signed the device out. An old session secret never signs a device back in. Start a new session.
A member who is not in the server
The connection is still saved. member.membership is not_member or left, playerSession is null, and member.joinUrl is where to send the player. The refresh token still works for Disconnect, and Renew a player session returns a player session once the member has joined.
Limits
One read per session every 2 seconds. Reads are never limited per IP address beyond the 600 requests a minute that every request of a game shares, so several players can poll from behind one IP address. No call to Discord is made, and data is written only when the session is approved.
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. A request with no Content-Type header is read as text, so it gets this response too |
401 | invalid_session_secret | Invalid session secret | sessionSecret is missing, is not a session secret, or is not the secret of a session of this game. A secret of another game's session is unknown here |
429 | rate_limited | Ask for the result of one session at most every 2 seconds | The result of this session was requested less than 2 seconds ago. The body adds retryAfter |
410 | result_gone | The result of this request can no longer be read. Start again | The session was approved more than 10 minutes ago, or what it approved has changed since: the member disconnected, moved to another player, or signed the device out. Start again |
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 of a game shares (the key, the state of the game, the origin, 405, 429, 500) are on the Errors page.

