CEEJAYDocs

Link an account

Connect a member's ID in your game, or on another platform, to their Discord ID.

POST /v1/members/link · requires members:link

Once an account is linked, activity on your platform can be tied to the community member, and you can name the member by playerId in Complete a quest task.

Body parameters

Prop

Type

Deprecated names that still work

SendDeprecated alias, still accepted
playerIdplatformUserId
playerNameplatformUsername

Send one of the two. If both are sent, the deprecated one is used. Deprecated names remain supported for as long as version 1 exists, and an error message names the one you sent.

Returns

Prop

Type

The response also includes the original field names. They contain the same values, and remain for as long as version 1 exists.

Deprecated field, still sentSame value as
idconnectionId
user_iddiscordId
platform_user_idplayerId
platform_usernameplayerName
is_activeactive
connected_atconnectedAt

Example

Request
curl -X POST https://ceejay.communitystudios.xyz/api/v1/members/link \
  -H "Authorization: Bearer $CEEJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "discordId": "409876543210987654",
    "platform": "roblox",
    "playerId": "88213004",
    "playerName": "novaplays"
  }'

The TypeScript example uses ceejay.members.link from the TypeScript SDK. It returns the data of the response below.

200 OK
{
  "success": true,
  "data": {
    "id": 311,
    "server_id": "721104938271003842",
    "user_id": "409876543210987654",
    "platform": "roblox",
    "platform_user_id": "88213004",
    "platform_username": "novaplays",
    "is_active": true,
    "connected_at": "2026-10-01T09:14:22.118Z",
    "connectionId": "311",
    "discordId": "409876543210987654",
    "playerId": "88213004",
    "playerName": "novaplays",
    "active": true,
    "connectedAt": "2026-10-01T09:14:22.118Z",
    "duplicate": false
  }
}

Known platforms

platform must name a platform that is known. There are three ways for it to be:

  • CEEJAY knows it by name. These are github, twitch, steam, twitter, xbox, playstation, metamask, epic, youtube, riot, battlenet, roblox, reddit, kick, patreon, tiktok, bluesky, lastfm and spotify. An X account is linked as twitter, and a wallet as metamask.
  • Your community added it as a custom platform. Someone on its team presses Add Platform under Settings → Developers → Platforms and saves the platform in API Link mode. The value to send is the platform's Slug, the name it is stored under, shown on its card there: lower-case letters, digits and underscores, up to 50 characters.
  • Your community already has at least one link for it, so a name you link under today keeps working.

Community details lists the first two under platforms. Any other name returns 400 with the code unknown_platform, and nothing is linked. This stops a typing mistake from becoming a platform of its own.

The name is trimmed and put in lower case before it is checked, so Twitch and twitch are the same platform. It can contain letters, digits and underscores, and nothing else.

A platform that is a game

On an instance that offers Connect, a game set up for your community is a platform from the day it is created: send the game's slug as platform. Use it when your players signed in to your game with Discord, so you already know which member each player is.

A link under a game's slug is a game connection at the verified level, vouched for by your server. Some things differ from any other platform:

  • playerId has to be an ID a game can use: at most 128 characters, starting with a letter or a digit, then letters, digits and . _ : @ | + = -. Any other ID returns 400 with the code bad_player_id.
  • The game has to be one your key can use. A paused game returns 409 with the code game_paused, a key limited to other games 403 with game_not_allowed, a game made for children 403 with audience_not_allowed, and a game that was removed 404 with game_not_found.
  • A member who ended their connection to the game themselves cannot be linked to it again by a key: 409 with the code member_disconnected. The restriction clears when the member connects again.
  • A member whose connection to the game ended because the player is under age cannot be linked to it again by a key either: 403 with the code under_age. This request does not include age information, so it cannot indicate that this has changed. The restriction clears when the member connects again through Connect.
  • A player the community blocked cannot be linked: 409 with the code player_blocked.
  • A connection the member made themselves through Connect is protected the way a sign-in is: a call that names a different playerId returns 409 with the code linked_by_sign_in.
  • The member is shown no page and gives no consent here. The game gets no player session for them until they have consented once through Connect.
  • The community's own reward for linking an account is not granted for a game. CEEJAY's bot processes the connection instead, shortly after the response, as it does any connection to the game: it sends player.connected, and where the connection meets the proof the game requires, it also adds the account.linked entry (once for each member and game), releases rewards that were waiting for a linked account of this game, and grants the game's own reward for connecting, if one is set up. See Game rewards.

Every other platform name is handled as before.

The member has to be in your server

A link is made for a member of your server only. A member CEEJAY already has a profile for passes. An ID it has no profile for is looked up in Discord first:

  • A member of your server passes.
  • An ID that is not in your server returns 404 with the code member_not_found. A member who has only just joined can take up to 15 seconds to be found. Wait 15 seconds before retrying.
  • When the Discord API is unavailable, the call returns 503 with the code discord_unavailable. Retry shortly.

Nothing is linked in the last two cases. If your game links an account the moment a player joins your server, be ready for one 404 and retry after 15 seconds.

The ID

playerId is trimmed before anything else: spaces at either end are removed, and an ID that is empty after that is rejected. Capitals and spaces at the ends are ignored whenever CEEJAY looks up the member an account is linked to, so 0xAbC and 0xabc are one account, as they are in Complete a quest task.

Two cases return 409, and nothing is changed:

  • The member linked the platform by signing in. A member who connected an account themselves, by signing in to the platform, has proved it is theirs. While that link is active, a call that names a different playerId for that member and platform returns the code linked_by_sign_in. Capitals and spaces at the ends do not make an ID different. Sending the same ID again is fine, and updates the name: the link keeps the capitalisation the member signed in with, and playerId in the response is the stored value. Once the member has disconnected the account, the platform can be linked again through the API. That link is a new one made by your key, even when it names the same account: it does not restore the member's sign-in. A link made by signing in before version 1.0.0, on a platform whose sign-in gives CEEJAY no token (Steam, wallets and Bluesky), cannot be distinguished from one made through the API and can still be replaced.
  • Another member has the account. An account belongs to one member of your server at a time. A playerId that another member has an active link for, on the same platform, returns the code player_already_linked. A link that was disconnected does not block this. Concurrent calls that send one account for two members are processed one at a time: the first links it, and the second gets this error. A member who already has an active link for that account can always send it again: their link is updated as before.

An account moves from one member to another only after the first member's link is disconnected. The API has no request that disconnects a link. A link the API points at a different account no longer carries the old account's sign-in, and neither does a link the API makes again after the member disconnected it. The member signs in to the platform again to connect it that way.

  • A member has one link per platform. Linking the same platform again replaces the ID, keeps the existing name if you send none, and reactivates the link if it had been disconnected.
  • The link shows in the member's connections in Get a member.
  • The first time a member links a platform, subscribers to the account.linked webhook get an event.
  • To name a member by an X handle, a wallet, a GitHub login or a Twitch login in Complete a quest task, link that account under the platform twitter, metamask, github or twitch.
  • Rewards that were waiting for this linked account are fulfilled now. If your community grants a reward for linking an account, it is granted once per member per platform.
  • For a platform that is a game, the reward for linking an account is not granted and the rest works differently: see A platform that is a game.

Retrying safely

Send an idempotencyKey with every call you might need to retry, such as after a timeout. It is a string of 1 to 128 characters that you choose, sent in the JSON body: an Idempotency-Key header is not read.

  • Retrying with the same key returns the original response. When the same request is sent again with its key, CEEJAY returns the original response, with duplicate: true and the header Idempotent-Replayed: true.
  • Each key is tied to one request payload. The same request means the same member, platform, ID and name. Two requests for one event need two keys, for example match-9:coins and match-9:xp. A key that was used for a different request, here or on any other request of the API, returns 409 with the code idempotency_key_reused, and nothing is changed.
  • A rejected request does not use up the key. A call that was rejected can be retried with the same key once the cause is fixed.
  • A key belongs to the API key that sent it. Another API key of your community can send the same text, and it is a different key.
  • Keys are retained for 30 days. Use a value you can regenerate after a crash, such as an order ID or a match ID.
  • On an instance that does not support idempotency keys yet, a call that sends one returns 503 with the code idempotency_unavailable, and nothing is changed. Community details reports whether keys are supported, under features.retryKeys. idempotency_key is not accepted on this endpoint; use idempotencyKey.
  • Without a key, a retried call is a new call.

Linking is also safe to repeat without a key: the same link sent twice updates the link it made.

Errors

StatuscodeerrorWhen
400bad_requestThe request body must be a JSON objectThe 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
400bad_requestdiscordId must be a valid Discord snowflake (17-20 digits)discordId is missing or is not a string of 17 to 20 digits. A JSON number is rejected
400bad_requestplatform is requiredplatform is missing, empty or not text
400bad_requestplayerId is requiredplayerId is missing, is not text, or is empty once its spaces are removed
400bad_requestplatformUserId is requiredThe same check, when you sent the ID as platformUserId. Every message about the ID or the name uses the name you sent
400bad_requestplayerName must be a stringplayerName was sent and is not text. A number is rejected
400bad_requestplatform cannot exceed 50 charactersplatform is longer than 50 characters, as sent or once it is put in lower case
400bad_requestplayerId cannot exceed 255 charactersID over 255 characters
400bad_requestplayerName cannot exceed 255 charactersName over 255 characters
400bad_requestplatform cannot contain a null characterplatform contains a null character (U+0000), which cannot be stored
400bad_requestplayerId cannot contain a null characterA null character in the ID
400bad_requestplayerName cannot contain a null characterA null character in the name
400bad_requestplatform can only contain letters, digits and underscoresplatform contains a space, a hyphen or any other character
400bad_requestidempotencyKey must be a string of 1-128 charactersThe key is empty, longer than 128 characters or not text, or contains an unpaired surrogate, such as the JSON "\ud800"
400bad_requestidempotencyKey cannot contain a null characterThe key contains a null character (U+0000)
400unknown_platformUnknown platform. Add it as a custom platform under Settings, Developers, Platforms, then link againThe platform is not one CEEJAY knows by name, not a custom platform of your community, and one your community has no link for: see Known platforms
403community_not_activeThis community is no longer active on CEEJAYThe platform is a game, and the community removed CEEJAY from its server
403game_not_allowedThis API key is limited to other gamesThe platform is a game, and the key is limited to other games of the community
403audience_not_allowedA game made for children cannot connect players or send player dataThe platform is a game that is set up as made for children
403under_ageThis player cannot be connectedThe platform is a game, and this member's connection to it ended because the player is under age. Only the member can lift this, by connecting again through Connect
409game_pausedThis game is pausedThe platform is a game that is paused or suspended
404game_not_foundGame not foundThe platform is the slug of a game that was removed. Nothing is linked under it again
400bad_player_idplayerId must be 1 to 128 characters: letters, digits and . _ : @ | + = -, starting with a letter or a digitThe platform is a game, and playerId is longer than 128 characters or contains a character outside the allowed set for a game's IDs. The body includes field
404member_not_foundMember not foundCEEJAY has no profile for the ID, and it is not a member of your server. Nothing was changed. A member who has only just joined can take up to 15 seconds to be found: wait 15 seconds before retrying
404not_availableThis is not available on this instance yet.This feature is not available on this instance yet. Community details reports the same under features. The body includes feature
409linked_by_sign_inThe member linked this platform by signing in, so only they can change itThe member linked this platform themselves by signing in, the link is active, and the call names a different playerId
409player_already_linkedThis playerId is already linked to another member in this serverAnother member of your server has an active link for this playerId on this platform
409player_blockedThis player cannot be connected until the community lifts a blockThe platform is a game, and the community blocked this player
409member_disconnectedThe member disconnected this game themselves, so only they can connect it againThe platform is a game, and this member ended their connection to it themselves. Only they can connect it again
409idempotency_key_reusedThis idempotencyKey was already used for a different requestYour API key already used this idempotencyKey for a different request: a request of another kind, or this one with other values. Nothing was changed
409idempotency_in_progressThe first request with this idempotencyKey is still running. Try again in a few seconds.The first request with this key is still being processed. Wait retryAfter seconds and retry
503discord_unavailableCould not check with Discord that this ID is a member of the server, so nothing was changedCEEJAY has no profile for the ID and could not confirm with Discord whether it is a member of your server. Retry with a pause between attempts. If it keeps failing, CEEJAY's bot may no longer be in your server
503idempotency_unavailableidempotencyKey cannot be used on this community yet, so nothing was changedYou sent a key, and this instance does not support idempotency keys yet. Nothing was changed. Community details reports it as features.retryKeys

The checks run in that order, and nothing is linked when one of them fails.

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 shares (401, 403, 405, 429, 500) are on the Errors page.

On this page

Community StudiosPrivacyTerms