Deduct program XP
Remove XP from a member inside a community program. Never drops below zero.
POST /v1/xp/program/deduct · requires xp:program:deduct
Program XP belongs to one program, such as an ambassador or creator program. It is separate from the member's server XP and level: deducting it changes neither.
Body parameters
Prop
Type
List programs gives you each program's programId.
Who it can be taken from
The call is rejected unless both of these conditions are met:
- The program is active. A program that is a draft, paused or archived returns
409with the codeprogram_not_active. - The member is in the program with the status Accepted or Active. A member with another status (such as Applied, Graduated or Removed) returns
409with the codemember_status_not_allowed, and a member who is not in the program returns404with the codemember_not_enrolled.
Deducting more than the member has leaves their program XP at zero: xpDeducted reports what was actually removed.
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 sent | Same value as |
|---|---|
totalXP | programXp |
previousXP | previousProgramXp |
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/xp/program/deduct \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"programId": 3,
"discordId": "409876543210987654",
"amount": 50,
"reason": "Reversed a duplicate award",
"idempotencyKey": "reversal-10444"
}'The TypeScript example uses ceejay.xp.program.deduct from the TypeScript SDK. It returns the data of the response below.
{
"success": true,
"data": {
"programId": 3,
"programName": "Ambassadors",
"discordId": "409876543210987654",
"previousXP": 500,
"xpDeducted": 50,
"totalXP": 450,
"reason": "Reversed a duplicate award",
"auditId": "10445",
"programXp": 450,
"previousProgramXp": 500,
"duplicate": false
}
}What a deduction does
- Deducts the XP from the member's program XP, which shows in the program's Members tab. Concurrent changes are all counted.
- Logs the change in the program's activity log, marked as made through the API with the key that made it, and writes an audit entry (
auditId). The XP, the activity log line and the idempotency key are committed in a single transaction. - Does not send a webhook event, does not count toward season XP and does not trigger level rewards.
- There is no request that reads program XP. Each change returns the new total.
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 program, member, amount and reason. 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.
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 | programId must be a positive integer | programId is missing, zero, negative, fractional, text that is not digits, or digits written with a leading zero |
400 | bad_request | programId cannot exceed 2147483647 | programId is above 2,147,483,647 |
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 | amount must be a positive integer | amount is missing, zero, negative, fractional or sent as text |
400 | bad_request | amount cannot exceed 100000 | More than 100,000 in one call |
400 | bad_request | reason cannot contain a null character | reason contains a null character (U+0000), which cannot be stored |
400 | bad_request | reason must be a string | reason was sent and is not a string: a number, a list, an object, true or false. null counts as left out |
400 | bad_request | reason must be valid Unicode text | reason contains an unpaired surrogate, such as the JSON "\ud800" |
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) |
404 | program_not_found | Program not found | No program with this ID in your community |
404 | member_not_enrolled | Member is not enrolled in this program | The member is not in the program |
404 | not_available | This 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 |
409 | program_not_active | Program is not active | The program is a draft, paused or archived. programStatus says which |
409 | member_status_not_allowed | Member status does not allow XP changes | The member is in the program with a status other than Accepted or Active. memberStatus says which |
409 | xp_limit_reached | This change would take the member's XP past the largest number CEEJAY can store, so nothing was changed | The change would take the member's XP past 2,147,483,647, the largest number CEEJAY can store. Nothing was changed |
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 |
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 |
{
"error": "Program is not active",
"code": "program_not_active",
"requestId": "req_5f0c2a9b7d3e41f8a6b0c1d2",
"programStatus": "draft"
}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.

