CEEJAYDocs

Grant program XP

Award XP inside a community program, to a member who is in it.

POST /v1/xp/program/grant · requires xp:program:grant

Program XP belongs to one program, such as an ambassador or creator program. It is separate from the member's server XP and level: granting it changes neither.

Body parameters

Prop

Type

List programs gives you each program's programId.

Who can receive it

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 409 with the code program_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 409 with the code member_status_not_allowed, and a member who is not in the program returns 404 with the code member_not_enrolled.

Unlike Grant XP, this call never creates anything: the member has to be in the program already.

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
totalXPprogramXp
previousXPpreviousProgramXp

Example

Request
curl -X POST https://ceejay.communitystudios.xyz/api/v1/xp/program/grant \
  -H "Authorization: Bearer $CEEJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "programId": 3,
    "discordId": "409876543210987654",
    "amount": 100,
    "reason": "Hosted a community event",
    "idempotencyKey": "event-2026-41-host"
  }'

The TypeScript example uses ceejay.xp.program.grant from the TypeScript SDK. It returns the data of the response below.

200 OK
{
  "success": true,
  "data": {
    "programId": 3,
    "programName": "Ambassadors",
    "discordId": "409876543210987654",
    "previousXP": 400,
    "xpGranted": 100,
    "totalXP": 500,
    "reason": "Hosted a community event",
    "auditId": "10444",
    "programXp": 500,
    "previousProgramXp": 400,
    "duplicate": false
  }
}

What a grant does

  • Adds the XP to the member's program XP, which shows in the program's Members tab. Concurrent changes are all counted.
  • Checks the member last. An enrolled ID CEEJAY has no member profile for is looked up in Discord before anything changes: an ID that is not in your server returns 404 with the code member_not_found, and when the Discord API is unavailable the call returns 503 with the code discord_unavailable. A member who has only just joined can take up to 15 seconds to be found. Wait 15 seconds before retrying.
  • 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: true and the header Idempotent-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: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.

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_requestprogramId must be a positive integerprogramId is missing, zero, negative, fractional, text that is not digits, or digits written with a leading zero
400bad_requestprogramId cannot exceed 2147483647programId is above 2,147,483,647
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_requestamount must be a positive integeramount is missing, zero, negative, fractional or sent as text
400bad_requestamount cannot exceed 100000More than 100,000 in one call
400bad_requestreason cannot contain a null characterreason contains a null character (U+0000), which cannot be stored
400bad_requestreason must be a stringreason was sent and is not a string: a number, a list, an object, true or false. null counts as left out
400bad_requestreason must be valid Unicode textreason contains an unpaired surrogate, such as the JSON "\ud800"
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)
404program_not_foundProgram not foundNo program with this ID in your community
404member_not_enrolledMember is not enrolled in this programThe member is not in the program
404member_not_foundMember not foundThe ID is enrolled, but CEEJAY has no member profile for it and it is not a member of your server. Nothing is granted. 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
409program_not_activeProgram is not activeThe program is a draft, paused or archived. programStatus says which
409member_status_not_allowedMember status does not allow XP changesThe member is in the program with a status other than Accepted or Active. memberStatus says which
409xp_limit_reachedThis grant would take the member's XP past the largest number CEEJAY can store, so nothing was changedThe change would take the member's XP past 2,147,483,647, the largest number CEEJAY can store. Nothing was changed
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 changedThe ID is enrolled, CEEJAY has no member profile for it, and CEEJAY could not confirm with Discord whether it is a member of your server. Retry with a pause between attempts
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
409 Conflict
{
  "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.

On this page

Community StudiosPrivacyTerms