Create a connect session
Start a connect session from your own server for a player you have authenticated, or attach that player to a code a member created in Discord.
POST /v1/games/{game}/connect/sessions · requires players:connect
Use it when your server, not the game, talks to CEEJAY. The secret key verifies who the player is, so the session is at the verified level. See Connect for the full flow.
Path parameters
Prop
Type
Body parameters
Prop
Type
Returns
Prop
Type
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/games/rugby_manager/connect/sessions \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"playerId": "p_19",
"playerName": "Nova77",
"returnTo": "game"
}'The TypeScript tab calls ceejay.connect.createSession from the TypeScript SDK. It returns the data of the response below.
{
"success": true,
"data": {
"sessionId": "kT3v9QmZ1xY7bLw2RfA8cN",
"url": "https://ceejay.communitystudios.xyz/connect/s/kT3v9QmZ1xY7bLw2RfA8cN",
"qrUrl": "https://ceejay.communitystudios.xyz/connect/s/kT3v9QmZ1xY7bLw2RfA8cN/qr",
"code": "K7M-2QX",
"sessionSecret": "ceejay_cs_4f8a2c1e9b7d3e41f8a6b0c1d25f0c2a9b7d3e41f8a6b0c1d24f8a2c1e9b7d3e",
"expiresAt": "2026-10-08T18:14:11.000Z",
"status": "pending",
"alreadyConnected": false,
"member": null
}
}{
"success": true,
"data": {
"sessionId": "kT3v9QmZ1xY7bLw2RfA8cN",
"url": null,
"qrUrl": null,
"code": null,
"sessionSecret": "ceejay_cs_4f8a2c1e9b7d3e41f8a6b0c1d25f0c2a9b7d3e41f8a6b0c1d24f8a2c1e9b7d3e",
"expiresAt": "2026-10-08T18:14:11.000Z",
"status": "needs_game_confirmation",
"alreadyConnected": false,
"member": {
"displayName": "Nova",
"avatarUrl": "https://cdn.discordapp.com/avatars/409876543210987654/a1b2c3d4e5f60718293a4b5c6d7e8f90.png"
}
}
}What to do with the response
Pass url to the player's device, or show code with the URL to enter it at, or show the image at qrUrl. The player approves with their Discord account on CEEJAY's connect page. The session lasts 10 minutes, and starting one ends every earlier open session for the same player.
Share sessionSecret with your game only when the game should hold the player's tokens: with it the game reads the result with Get the result and gets a player session and a refresh token. A server that runs the whole flow never passes it on, and reads the session with Get a connect session.
Confirmation by the game
A connection is saved only after the Discord member and the game have both confirmed. The game's confirmation can come in three ways:
- From the game, with
sessionSecret, through Confirm connect. - From your server, through Confirm a connect session, after your game has shown the player the Discord name.
- Up front, with
confirmInGame: false. Send that only when the link is opened on the device the player is signed in on and the code is never shown where others can read it: the step it skips is the one where a player sees that someone else approved.
With a code
A member can start in Discord, where they get a code to enter in the game. When your game does not integrate CEEJAY directly, your server sends that code here with the player. No new session is created: the player is attached to the member's session, the code is cleared, and member identifies whose code it was. Show that name to the player before the game approves.
The code comes from the game's button in the community's Discord server: the member presses it and gets a code that works once and lasts 10 minutes, and then enters it in the game. Where the community has not posted the button, or the code has been used or has run out, no open request has it and code returns 404 with the code code_not_found.
A player who is already connected
When this player is already connected at the verified level with the member's own approval, nobody is asked again: status is approved, alreadyConnected is true, and url, qrUrl and code are null. A player your server linked with Link an account is not connected that way until the member has approved once, so the session takes the usual path.
Age
A game set up for a mixed audience must send underAge with every session. true returns 403 with the code under_age: nothing is stored, and a connection the player has is ended. A game set up as made for children connects nobody.
The request takes no idempotencyKey: a second call creates a second session and ends the first.
The scope
players:connect lets a server act for a player: it can create a pass, and with a pass any player who is already connected at the verified level can be signed in. Keep it on your own server. It is never part of *: a Full Access key returns 403 here. Tick Act for a player when you make the key.
Limits
The Connect requests of a server have a limit of their own: 600 requests a minute for each game, across all five Connect endpoints. They are not counted against the key's own requests per minute. One API key is also capped at 6,000 requests a minute across all five Connect endpoints. The three X-RateLimit headers report the game's limit of 600 once the request has resolved its game, and the key's limit of 6,000 on a response returned before that: see Rate limits.
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 |
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 |
403 | community_not_active | This community is no longer active on CEEJAY | The community removed CEEJAY from its server |
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 |
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 |
409 | game_paused | This game is paused | The game is paused or suspended |
400 | bad_player_id | playerId must be 1 to 128 characters: letters, digits and . _ : @ | + = -, starting with a letter or a digit | playerId is missing, is not text, is longer than 128 characters, or contains a character that is not allowed. A number is refused |
400 | bad_request | playerName must be text of at most 80 characters | playerName was sent and is not text, is longer than 80 characters, or contains a null character (U+0000) |
400 | bad_request | code must be text | code was sent and is not text |
400 | unknown_return | returnTo is not one of the return addresses saved for this game | returnTo is not the key of a return URL saved for the game |
400 | bad_request | locale must be a language tag such as en or pt-BR | locale was sent and is not a language tag of at most 12 characters |
400 | bad_request | underAge must be true or false | underAge was sent and is not true or false |
400 | bad_request | confirmInGame must be true or false | confirmInGame was sent and is not true or false |
400 | age_answer_required | This game is for a mixed audience, so underAge must be sent as true or false | The game is set up for a mixed audience and underAge was not sent |
403 | under_age | This player cannot be connected | underAge is true. Nothing is stored, and a connection the player has is ended when the player is verified |
404 | code_not_found | No open request has this code | No request started by a member in Discord for this game has this code: it is incorrect, it was already used, or its 10 minutes are over |
409 | session_locked | This request was locked after too many attempts. Start again | The code belongs to an open request that was locked by this fifth failed attempt on it |
The checks run in that order: the body, the game in the path, then the fields.
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.

