Renew a player session
Exchange the device's refresh token for a new player session and refresh token.
POST /v1/player/session · for public key
A player session lasts 15 minutes. Renew it when the game starts and shortly before the session expires.
Body parameters
Prop
Type
Returns
Prop
Type
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/player/session \
-H "Authorization: Bearer $CEEJAY_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "ceejay_rt_5512.1.4f8a2c1e9b7d3e41f8a6b0c1d25f0c2a9b7d3e41f8a6b0c1d24f8a2c1e9b7d3e"
}'The TypeScript example uses connect.playerSession from the Connect client in the TypeScript SDK. The client stores the refresh token, renews the player session before it expires and returns the current one, or null when this device has no connection.
{
"success": true,
"data": {
"playerSession": {
"token": "cjps1.eyJ2IjoxLCJhdWQiOiJnYW1lIiwicyI6IjcyMTEwNDkzODI3MTAwMzg0MiIsImciOjEyfQ.pV3mQ0b6k1y8ZtL2c9XwR4s7Nf5aHj0uEoGdBiKq3M",
"expiresAt": "2026-10-09T09:15:02.000Z"
},
"refreshToken": "ceejay_rt_5512.2.e3d7b9e1c2a8f42d1c0b6a8f14e3d7b9a2c0f52d1c0b6a8f14e3d7b9e1c2a8f4",
"refreshExpiresAt": "2027-01-07T09:00:02.000Z",
"membership": "member",
"joinUrl": null,
"sharing": {
"agreed": [
"progress",
"activity"
],
"needed": []
},
"manageUrl": "https://ceejay.communitystudios.xyz/connect/mine"
}
}{
"success": true,
"data": {
"playerSession": null,
"refreshToken": "ceejay_rt_5512.2.e3d7b9e1c2a8f42d1c0b6a8f14e3d7b9a2c0f52d1c0b6a8f14e3d7b9e1c2a8f4",
"refreshExpiresAt": "2027-01-07T09:00:02.000Z",
"membership": "left",
"joinUrl": "https://discord.gg/harbourtown",
"sharing": {
"agreed": [
"progress",
"activity"
],
"needed": []
},
"manageUrl": "https://ceejay.communitystudios.xyz/connect/mine"
}
}The refresh token is replaced each time
Every renewal returns a new refresh token, and the one you sent is no longer the current one. Three rules follow.
- Store the new refresh token before you use the new player session. Once the device has made a request with a player session of the new token, the old token is invalidated.
- Renew one request at a time, including across tabs of a browser game.
- When a response was lost, send the token you still have. The previous token is still accepted until the device has used a player session issued with the newer one, however long the device was offline.
A token that was replaced and is used after that returns 401 with the code refresh_reused, and the device is signed out, because the token may have been compromised. The player connects again. A malformed or unrecognised value returns 401 with the code invalid_refresh_token and changes nothing.
A refresh token is valid for 90 days from its last use. A connection supports at most 5 devices: when a sixth is added, the least recently used device is signed out.
What the response says about the member
membership | playerSession | What the game shows |
|---|---|---|
member | A token | Connected |
pending | A token | Connected. The member has not passed Discord's membership screening yet |
not_member | null | Show "Join the server" with joinUrl |
left | null | Show "Rejoin the server" with joinUrl |
When sharing.needed is not empty, the game has added a kind of data since the member consented. Send the player to manageUrl to consent to it.
When the connection has ended
401 with the code connection_ended means this device is no longer authorised. Discard the stored tokens and show the player as disconnected. reason explains why:
reason | What happened |
|---|---|
disconnected | The member, the community's team or the studio ended the connection |
moved | The member moved to another player, or another Discord account took this player |
signed_out | This device only: the member signed it out, a sixth device replaced it, or the token was replayed |
ended | Anything else |
Limits
10 renewals a minute for one device. No call to Discord is made.
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 error too |
401 | invalid_refresh_token | Invalid refresh token | refreshToken is missing, is not a refresh token issued by this instance, belongs to another game, or has not been used for 90 days. Nothing is changed for an unrecognised value |
401 | refresh_reused | This refresh token was used before, so the device has been signed out. Connect again | This token was replaced by a later renewal that the device has used since, so the token may have been compromised. The device is signed out: connect again |
401 | connection_ended | This connection has ended | The connection or this device was ended. The body includes reason: disconnected (by the member, the team or the studio), moved (the member moved to another player, or another account took this one), signed_out (this device only: the member signed it out, a sixth device replaced it, or the token was replayed) or ended (anything else) |
Every error body also includes requestId, and a 400 with the code bad_request identifies the invalid field in field. Match on code; message text 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.

