ceejay.xp
Read, grant and deduct a member's XP, for the whole server or inside a community program.
Every function takes the same last argument, options: signal, timeoutMs and
maxRetries, for that one call. A write also takes idempotencyKey there. Every function
can also raise the errors any request can raise, such as a bad key or the rate limit. See
Errors and retries.
Send each of the four writes on this page with an idempotencyKey. A grant that is sent
again with the same key is not granted a second time, and the SDK then retries
automatically after a failure. Without a key the SDK never sends a write twice: after a
failure, read the member's total with ceejay.xp.getBalance before you decide to retry.
ceejay.xp.getBalance
Returns a member's total XP and level, without the rest of their profile.
GET /v1/xp/balance · requires xp:read · API reference: Get XP balance
Retried by the SDK: Yes. It is a read
Parameters
Prop
Type
Returns Schemas['XpBalanceResult'].
Prop
Type
Errors
This request has no errors specific to it. A member CEEJAY has not seen is not an error:
xp and level are 0.
Example
const balance = await ceejay.xp.getBalance('409876543210987654')
console.log(balance.xp, balance.level)ceejay.xp.grant
Grants XP to a member. The response shows whether the member reached a new level.
POST /v1/xp/grant · requires xp:grant · API reference: Grant XP
Retried by the SDK: Only with idempotencyKey
Parameters: one object, then options.
Prop
Type
Options
Prop
Type
Returns Schemas['GrantXpResult'].
Prop
Type
The response also includes three legacy field names, which are still sent: totalXP
(same as xp), previousXP (same as previousXp) and currentLevel (same as level).
When the grant raises the member to a new level, CEEJAY grants that level's rewards and level roles asynchronously, shortly afterwards. The request does not wait for them.
Errors
| Status | code | When |
|---|---|---|
400 | bad_request | A field is missing or not valid, amount is above 100,000, or reason is not a string |
404 | member_not_found | CEEJAY has no profile for this ID, and Discord reports that it is not in your server. A member who has just joined may not be available for up to 15 seconds: retry after 15 seconds |
409 | xp_limit_reached | The grant would exceed the maximum value CEEJAY can store for a member's XP. Nothing was granted |
503 | discord_unavailable | CEEJAY has no profile for this ID and could not check the member with Discord. Nothing was granted. The SDK retries if you sent a key |
Idempotency errors are covered in Errors and retries.
Example
const grant = await ceejay.xp.grant({
discordId: '409876543210987654',
amount: 250,
reason: 'Won the Saturday tournament',
})
if (grant.leveledUp) {
console.log(`Level ${grant.previousLevel} to level ${grant.level}`)
}With a key, you or the SDK can safely retry the same grant:
const grant = await ceejay.xp.grant(
{ discordId: '409876543210987654', amount: 250, reason: 'Won match 881' },
{ idempotencyKey: 'match-881:xp' },
)
console.log(grant.duplicate ? 'Already given' : `Now at ${grant.xp} XP`)ceejay.xp.deduct
Deducts XP from a member. The total never goes below 0: deducting more than the member has leaves them at 0.
POST /v1/xp/deduct · requires xp:deduct · API reference: Deduct XP
Retried by the SDK: Only with idempotencyKey
Parameters: one object, then options.
Prop
Type
Options
Prop
Type
Returns Schemas['DeductXpResult'].
Prop
Type
The response also includes three legacy field names, which are still sent: totalXP
(same as xp), previousXP (same as previousXp) and currentLevel (same as level).
Errors
| Status | code | When |
|---|---|---|
400 | bad_request | A field is missing or not valid, or reason is not a string |
404 | member_not_found | CEEJAY has no profile for this member. Unlike a grant, a deduction never creates one, and does not check with Discord |
Idempotency errors are covered in Errors and retries.
Example
const deduction = await ceejay.xp.deduct({
discordId: '409876543210987654',
amount: 100,
reason: 'Left the match early',
})
console.log(deduction.xpDeducted, deduction.xp)ceejay.xp.program.grant
Grants XP to a member inside a community program, such as an ambassador program. Program XP is tracked separately from the member's server XP.
POST /v1/xp/program/grant · requires xp:program:grant · API reference: Grant program XP
Retried by the SDK: Only with idempotencyKey
Parameters: one object, then options.
Prop
Type
Options
Prop
Type
The program must be active, and the member must be in it.
Returns Schemas['GrantProgramXpResult'].
Prop
Type
The response also includes two legacy field names, which are still sent: totalXP (same
as programXp) and previousXP (same as previousProgramXp).
Errors
| Status | code | When |
|---|---|---|
400 | bad_request | A field is missing or not valid, or reason is not a string |
404 | program_not_found | There is no program with this ID on your server |
404 | member_not_enrolled | The member is not in the program |
404 | member_not_found | The ID is in the program, but is not a member of your server |
404 | not_available | Programs are not available on this instance |
409 | program_not_active | The program is not active. error.body includes the program's status as programStatus |
409 | member_status_not_allowed | The member's status in the program does not allow XP changes. error.body includes it as memberStatus |
409 | xp_limit_reached | The grant would exceed the maximum value CEEJAY can store for a member's program XP |
503 | discord_unavailable | CEEJAY could not check this ID with Discord. Nothing was changed |
Idempotency errors are covered in Errors and retries.
Example
const grant = await ceejay.xp.program.grant(
{
programId: 3,
discordId: '409876543210987654',
amount: 500,
reason: 'Hosted a community night',
},
{ idempotencyKey: 'community-night-2026-10-03:409876543210987654' },
)
console.log(grant.programName, grant.programXp)ceejay.xp.program.deduct
Deducts XP from a member inside a community program. The total never goes below 0.
POST /v1/xp/program/deduct · requires xp:program:deduct · API reference: Deduct program XP
Retried by the SDK: Only with idempotencyKey
Parameters: one object, then options.
Prop
Type
Options
Prop
Type
Returns Schemas['DeductProgramXpResult'].
Prop
Type
The response also includes two legacy field names, which are still sent: totalXP (same
as programXp) and previousXP (same as previousProgramXp).
Errors
| Status | code | When |
|---|---|---|
400 | bad_request | A field is missing or not valid, or reason is not a string |
404 | program_not_found | There is no program with this ID on your server |
404 | member_not_enrolled | The member is not in the program |
404 | not_available | Programs are not available on this instance |
409 | program_not_active | The program is not active. error.body includes the program's status as programStatus |
409 | member_status_not_allowed | The member's status in the program does not allow XP changes. error.body includes it as memberStatus |
409 | xp_limit_reached | The change would exceed the maximum value CEEJAY can store |
Idempotency errors are covered in Errors and retries.
Example
const deduction = await ceejay.xp.program.deduct({
programId: 3,
discordId: '409876543210987654',
amount: 50,
})
console.log(deduction.xpDeducted, deduction.programXp)
