Grant a role
Add a Discord role to a member, for example for verification, subscriber perks or tournament placement.
POST /v1/roles/grant · requires roles:manage
Body parameters
Prop
Type
Returns
Prop
Type
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/roles/grant \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"discordId": "409876543210987654",
"roleId": "982371004827100394"
}'The TypeScript example uses ceejay.roles.grant from the TypeScript SDK. It returns the data of the response below.
{
"success": true,
"data": {
"discordId": "409876543210987654",
"roleId": "982371004827100394",
"action": "granted",
"duplicate": false
}
}How it works
CEEJAY looks up the role in your server, then asks Discord to add it to the member and responds once Discord has done so. A member who already has the role is not an error. The call changes the role and nothing else: it grants no XP or currency, and it sends no webhook event of its own.
The change shows in your server's audit log in Discord, made by the CEEJAY bot, with the reason API key: followed by the name of the key that made the call.
Roles a key can change
A key can grant or revoke an ordinary role of its own server, and no other. The role is checked before Discord is asked to change anything:
- A role that is not in your server returns
404with the coderole_not_found. - The
@everyonerole returns403with the coderole_not_allowed. - A role that Discord or another app manages, such as a bot's own role or the server booster role, returns
403with the coderole_not_allowed. - A role that can moderate or manage the server returns
403with the coderole_not_allowed. That is a role with any of these permissions: Administrator, Kick Members, Ban Members, Timeout Members, Manage Server, Manage Channels, Manage Roles, View Audit Log, Manage Messages, Manage Nicknames, Manage Webhooks, Manage Expressions, Manage Events, Manage Threads, Mute Members, Deafen Members or Move Members. - A role that can mention everyone returns
403with the coderole_not_allowed, in a server whose@everyonerole cannot. That is a role with the Mention @everyone, @here, and All Roles permission. Where the@everyonerole has that permission itself, every member can already mention everyone, a role that also has it adds nothing, and it is not rejected. Discord grants@everyonethis permission in a new server, and a new role starts with the permissions of@everyone, so in a server that never disabled it most roles have it. - A bot master role returns
403with the coderole_not_allowed. That is a role your community added under Settings → General → Team Management, in Additional Bot Master Roles. Anyone with this role has full access to the CEEJAY dashboard, whatever its Discord permissions are: see Team management.
List roles shows, for every role in your server, whether a key may change it and, if not, why. A role that has moderation permissions only through channel permission overwrites cannot be seen from the role list.
CEEJAY reads your server's role list from Discord and caches it for up to a minute. A role you created a moment ago is found immediately. A change to a role's permissions can take up to a minute to apply. When the list cannot be read, the call returns 503 with the code roles_unavailable and nothing is changed.
The bot master roles are read on every call and never cached, so a role your community adds there is rejected immediately. When they cannot be read, the call returns 503 with the code dashboard_roles_unavailable, and nothing is changed.
Keep a key with roles:manage on your own backend all the same, and never in a game client.
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: trueand the headerIdempotent-Replayed: true. - Each key is tied to one request payload. The same request means the same member and role. Two requests for one event need two keys, for example
match-9:coinsandmatch-9:xp. A key that was used for a different request, here or on any other request of the API, returns409with the codeidempotency_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
503with the codeidempotency_unavailable, and nothing is changed. Community details reports whether keys are supported, underfeatures.retryKeys.idempotency_keyis not accepted on this endpoint; useidempotencyKey. - Without a key, a retried call is a new call.
Granting a role is idempotent, so when a first request was started more than a minute ago and never finished, a retry with the same key makes the change again and returns normally. The request is also safe to repeat without a key.
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 |
400 | bad_request | discordId 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 |
400 | bad_request | roleId must be a valid Discord snowflake (17-20 digits) | roleId is missing or is not a string of 17 to 20 digits |
400 | bad_request | idempotencyKey must be a string of 1-128 characters | The key is empty, longer than 128 characters or not text, or contains an unpaired surrogate, such as the JSON "\ud800" |
400 | bad_request | idempotencyKey cannot contain a null character | The key contains a null character (U+0000) |
400 | discord_error | Discord API error: 400 | Discord rejected the request. details contains Discord's response |
403 | bot_missing_permission | Bot lacks permission to manage this role | The bot has no Manage Roles permission, or its own role is below the role you named in the role hierarchy |
403 | role_not_allowed | Roles that can moderate or manage the server cannot be given or removed through the API | The role can moderate or manage the server: see Roles a key can change |
403 | role_not_allowed | Roles that can mention everyone cannot be given or removed through the API | The role can mention @everyone, @here and all roles, and your server's @everyone role cannot |
403 | role_not_allowed | Roles managed by Discord or by another app cannot be given or removed through the API | The role is managed by Discord or by another app, such as a bot's own role or the server booster role |
403 | role_not_allowed | The @everyone role cannot be given or removed | roleId is your server's own ID, which is the ID of its @everyone role |
403 | role_not_allowed | Roles that give access to the CEEJAY dashboard cannot be given or removed through the API | The role is one of your community's bot master roles |
404 | role_not_found | Role not found in this server | The role is not one of your server's |
404 | member_not_found | Member or role not found in this server | Discord found no such member in your server. The role was found a moment before, so the member is not in the server. A member who has only just joined can take up to 15 seconds to be found. Wait 15 seconds before retrying. |
409 | idempotency_key_reused | This idempotencyKey was already used for a different request | Your API key already used this idempotencyKey for a different request: a request of another kind, or this one with other values. Nothing was changed |
409 | idempotency_in_progress | The 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 |
429 | discord_rate_limited | Discord API rate limited, try again later | Discord is rate limiting the bot. retryAfter and the Retry-After header contain the wait Discord requested, in whole seconds, if it gave one. Do not retry in a tight loop |
500 | discord_not_configured | This instance cannot reach Discord yet | This instance has no connection to Discord yet |
503 | roles_unavailable | Could not read this server's roles from Discord, so nothing was changed | CEEJAY could not read your server's role list from Discord. Nothing was changed. Retry with a pause between attempts |
503 | discord_unavailable | Discord did not answer in time, so the role may or may not have been given | Discord did not respond to the change within 10 seconds. The change may or may not have been made. The idempotency key stays reserved and the request is safe to repeat |
503 | dashboard_roles_unavailable | Could not check whether this role gives access to the CEEJAY dashboard, so nothing was changed | CEEJAY could not read your community's bot master roles. Nothing was changed. Retry with a pause between attempts |
503 | idempotency_unavailable | idempotencyKey cannot be used on this community yet, so nothing was changed | You sent a key, and this instance does not support idempotency keys yet. Nothing was changed. Community details reports it as features.retryKeys |
| Discord's | discord_error | Discord API error: 503 | Any other status Discord returns is passed on, with Discord's own status. details contains Discord's response. After a 5xx the change may or may not have been made |
Discord's own rate limit returns 429 with the code discord_rate_limited. That response does not mean your key's own limit is used up: X-RateLimit-Remaining still shows what the key has left.
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.
Bot permissions
The CEEJAY bot must have Manage Roles and its role must be higher than the target role in the
role hierarchy. If it doesn't, this returns 403 with the code bot_missing_permission: move the CEEJAY role higher in
Server Settings → Roles.

