Introduction
Award XP, move currency, complete quests, manage Discord roles, deliver shop items and receive real-time events, from your own game, app or platform.
The CEEJAY API is a REST API over HTTPS. Every request is authenticated with a secret API key, all responses are JSON, and each key is bound to a single Discord server. Events flow the other way as signed webhooks, and AI agents can connect to the same community over MCP.
One part is different: the requests a game makes itself, for Connect and for the game loop, take a public key that ships inside the game or a player session, not a secret key.
Base URL
Every request goes to your dashboard's URL followed by /api/v1. One older endpoint is outside it, and is noted below.
On the shared CEEJAY service the base URL is:
https://ceejay.communitystudios.xyz/api/v1On a dedicated instance it is your instance's dashboard URL followed by /api/v1. Use
the URL you see in your browser when you open your dashboard, for example
https://dashboard.example.com/api/v1. A key only works on the instance
where it was created.
Each reference page shows its endpoint as a path that starts with /v1, such as
/v1/xp/grant. The full URL is your dashboard's URL, then /api, then that path. On
the shared service, /v1/xp/grant is https://ceejay.communitystudios.xyz/api/v1/xp/grant.
One endpoint is outside /v1: the older Complete a quest task endpoint. Its page shows the whole path, /api/quests/external/complete, and that path goes straight after your dashboard's URL. The base URL above does not reach it. It still works; new code uses /v1/quests/tasks/complete.
Start with Community details
Make Community details your first call. It needs no scope, and returns which community your key belongs to, what the key may do, and which requests your instance supports.
What you can do
Community details
Find out which community a key belongs to, what the key may do and what your instance supports.
Members
Read a member's profile, XP, balance and linked accounts. Link a member's ID in your game to their Discord ID.
Connect
Link a player of your game to their Discord account, from inside the game, with confirmation from both sides.
The game loop
Show the community's quests, shop, locker, rank and daily reward in your game, and sell from its shop with the player's coins.
Player data
Send fields and events about your players, read a player, and delete a player's data, once they are connected. The field map says what data the community stores.
XP
Grant and deduct a member's XP, and read their level.
Program XP
List programs, and grant and deduct XP inside one, such as an ambassador program.
Economy
Grant, deduct and read a member's currency balance, with idempotency keys so a retry never grants twice.
Quests
List quests, mark one completed, or report that a member finished a quest task your platform checks.
Roles and messages
List, grant and revoke Discord roles, list your channels, and post messages or embeds to them.
Shop delivery
Deliver shop items from your own server. CEEJAY sends a signed request when a member buys, you confirm the delivery, and a list shows the deliveries awaiting your confirmation.
Webhooks
Receive signed events the moment something happens in your community, and send your own.
AI agents over MCP
Let Claude Code, Codex or Cursor read your community and draft changes, within the limits you set.
TypeScript SDK
Call the API from your own server with typed functions, check webhook and shop delivery signatures, and connect a game's players to Discord. Not published yet.
Changelog
What changed in the API, and what you can rely on staying the same.
Not in this API yet
The REST API does not expose analytics. Member growth, message volume, sentiment, retention and voice-of-community data live in the dashboard and in scheduled reports. There are no REST endpoints for them. AI agents can read them through MCP.
If you need to pull analytics into a warehouse or BI tool such as Power BI, talk to us. That's a separate surface and we'd rather design it around your actual reporting needs.
Conventions
- Use HTTPS for every request.
- Request and response bodies are JSON, and the body of a write is a JSON object. Send
Content-Type: application/jsonon every write. Without it the body is not read as JSON, and the response is a400:The request body must be a JSON object, or a message that names a field as missing when the body arrives as a form, which is whatcurl -dsends when you set no content type. - Write each path exactly as its reference page shows it, without a trailing slash. A trailing slash returns
308with a redirect to the path without it, and a path that does not exist returns404with an HTML page, not JSON. - Discord IDs are snowflakes: numeric strings of 17 to 20 digits. Always send them
as strings, never as JSON numbers; they exceed the safe integer range in JavaScript, so
an ID sent as a number is refused with a
400. Amounts and other counts are JSON numbers, in requests and in responses. - Every successful response is wrapped as
{ "success": true, "data": … }.datais an object, or a list on the requests that list things, which add"page": { "nextCursor", "limit" }beside it: sendnextCursorback ascursorfor the next page, and stop when it isnull. Three responses also keep the fields they had besidesuccessbefore: List quests (total,limitandoffset), Complete a shop delivery and Complete a quest task. - Each field has one current name, in camelCase, in requests and in responses. The member is
discordId, a game's own ID for the member isplayerId, and a note isreason. Every legacy field name is kept for backwards compatibility and is still accepted and still returned: each reference page lists them. When both names of a field are sent, the older one is used. - Within version 1 nothing is renamed or removed. See the Changelog.
- Times are ISO 8601 strings in UTC, such as
2026-10-03T09:14:22.118Z. - Errors return a non-2xx status and
{ "error": "…", "code": "…", "requestId": "…" }, sometimes with extra fields. Act on thecode, which never changes, not on theerrormessage text. A few responses that come before your key is read are not JSON. See Errors. - Every response includes headers showing your key's remaining rate limit this minute, in
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset, and includesX-Request-Id. See Rate limits. - Retries: every request that creates or changes data takes an
idempotencyKeyin its body. Sent again with the same key, it returns the original response, withduplicate: true, and changes nothing. One key is one request, so two requests for one event need two keys. Without a key, a repeated XP grant, message or event happens twice. The requests about players and the field map take no key: setting a field again changes nothing, and an event has its owneventId.
Getting help
Every request made with a key CEEJAY accepts is logged against that key: the endpoint,
the method, the status we returned and the caller's IP address. That includes a request
refused for a missing scope (403). Of the requests refused by the rate limit (429), the first
in each one-minute window is logged and the rest are not. A request with a missing, unknown,
revoked or expired key is not logged. If something looks wrong, send us the requestId of the
response, or the time and the endpoint, and we can trace the call.

