CEEJAYDocs

ceejay.quests

List quests, complete one for a member, or report a quest task your own platform checked.

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.

Which of the two to call

You want toCall
Complete a whole quest for a member, and vouch that they did itceejay.quests.complete
Report one thing a member did, for a quest task of the type Completed by your APIceejay.quests.reportTask

ceejay.quests.complete accepts the quest's ID. ceejay.quests.reportTask accepts the task key the quest builder shows on the task, and CEEJAY determines which quests it finishes.

ceejay.quests.list

Lists the quests set up on your server, a page at a time, with their rewards and rules.

GET /v1/quests/list · requires quests:read · API reference: List quests

Retried by the SDK: Yes. It is a read

Parameters: one object, which can be left out, then options.

Prop

Type

There are two ways to paginate. Use one of them. With cursor, each quest is returned exactly once, even when quests are added while you page. With offset, you skip a number of quests.

Returns the full response, typed as Schemas['ListQuestsResponse']. The quests are in data, and page, total, limit and offset describe the pagination.

Prop

Type

Each quest, typed as Schemas['Quest']:

Prop

Type

And the rules ceejay.quests.complete applies to it:

Prop

Type

The community's daily quest limit is not on a quest: ceejay.community.get reports it.

Each quest also includes these legacy field names, in lower case with underscores, which are still sent: icon_emoji, quest_type, xp_reward, currency_reward, role_reward_id, badge_id, is_repeatable, max_completions, max_completions_per_user (now maxCompletionsPerMember), starts_at, ends_at, verification_type, program_id, created_at, repeat_cooldown_hours, required_level, required_role_id, prerequisite_quest_id and required_season_xp.

Errors

StatuscodeWhen
400bad_requestlimit or offset is not a whole number of 0 or more, or cursor is not one this function returned
404not_availableQuests are not available on this instance

Example

ceejay.quests.list
const page = await ceejay.quests.list({ category: 'integrations', limit: 20 })

console.log(`${page.data.length} of ${page.total} quests`)
for (const quest of page.data) {
  console.log(quest.id, quest.title, quest.xpReward, quest.currencyReward)
}

To read every quest, follow page.nextCursor until it is null:

ceejay.quests.list, every page
let cursor: string | null = null
do {
  const page = await ceejay.quests.list({ limit: 100, ...(cursor ? { cursor } : {}) })
  for (const quest of page.data) console.log(quest.id, quest.title)
  cursor = page.page.nextCursor
} while (cursor)

ceejay.quests.complete

Marks a quest completed for a member and grants its rewards. CEEJAY applies the quest's own rules first, the ones it applies when a member does the quest in Discord. The quest's own verification and review are not run: when you call, you vouch that the member did it.

POST /v1/quests/complete · requires quests:complete · API reference: Complete a quest

Retried by the SDK: Only with idempotencyKey

Parameters: one object, then options.

Prop

Type

Options

Prop

Type

Without a key, a second request is processed as a new completion and checked like one. With a key, the SDK retries automatically after a 429, a 5xx or no response. The API also accepts the key as idempotency_key, for code that calls it without the SDK.

Returns Schemas['CompleteQuestResult'].

Prop

Type

The response also includes the run under its legacy name, progress, with its stored field names. It is still sent. Use run.

rewards shows what was granted at the time of the response:

Prop

Type

What happens after the response. CEEJAY then assigns the quest's role, grants the extra rewards set on the quest in the Rewards Engine, and sends the quest.completed webhook. The call does not wait for this, and still returns a response when CEEJAY's bot is down: these are processed when the bot is back online.

If you call ceejay.roles.grant, ceejay.xp.program.grant or ceejay.economy.grant yourself after a completion, to add what this function used to leave out, stop doing that: the member would now receive it twice.

Errors

If a rule of the quest is not met, the request is rejected and nothing is completed. The code identifies the rule. The rules are checked in the order of this table, and the first one that is not met is returned. Three of them return 400, as they always have, and the others return 409: test the code, not the status. Some errors include the state that caused them in error.body.

StatuscodeWhenAlso in error.body
400quest_not_activeThe quest is not active
409quest_not_startedThe quest has not started yetstartsAt
409quest_endedThe quest has endedendsAt
409quest_requires_roleThe member lacks the role the quest requiresrequiredRoleId
409quest_requires_levelThe member has not reached the level the quest requiresrequiredLevel, level
409quest_requires_season_xpThe member has not earned the season XP the quest requiresrequiredSeasonXp, seasonXp, seasonRunning
409quest_requires_programThe member is not in the program the quest belongs toprogramId
409quest_fullThe quest has reached its overall completion limitmaxCompletions
400quest_already_completedThe member completed it, and it cannot be repeated
400quest_limit_reachedThe member reached the quest's limit for one member
409quest_cooling_downThe member has to wait before completing it againavailableAt
409quest_requires_questThe member has not completed the quest that comes firstprerequisiteQuestId
409quest_daily_limit_reachedThe member has reached the community's daily quest limitdailyQuestLimit, completedToday, resetsAt

The daily quest limit counts the quests a member completes in one UTC day, across all quests. It is 5 unless the community set another. It is a rule about starting: a run the member started in Discord before they reached the limit still completes.

The other errors:

StatuscodeWhen
400bad_requestA field is missing or not valid, or proof is nested more than 32 levels deep
404quest_not_foundThere is no quest with this ID on your server
404member_not_foundThe ID is not a member of your server. A member who has just joined may not be available for up to 15 seconds: retry after 15 seconds
404not_availableQuests are not available on this instance
409xp_limit_reached, balance_limit_reachedA reward would exceed the maximum value CEEJAY can store. Nothing was completed
503discord_unavailableCEEJAY could not check this ID with Discord. Nothing was completed
503member_roles_unavailableThe quest requires a role, and CEEJAY could not read the member's roles from Discord. Nothing was completed

Idempotency errors are covered in Errors and retries.

Example

ceejay.quests.complete
import { CeejayError } from '@communitystudios/ceejay'

try {
  const done = await ceejay.quests.complete(
    { discordId: '409876543210987654', questId: 42, proof: { matchId: 'm_8841', score: 12 } },
    { idempotencyKey: 'quest-42-match-m_8841' },
  )
  console.log(done.questTitle, done.rewards.xp, done.rewards.currency, done.run?.id)
  if (done.rewards.currencySkipped) console.log('The economy is switched off, so no coins were paid')
} catch (error) {
  if (error instanceof CeejayError && error.code === 'quest_already_completed') {
    console.log('The member already has this quest')
  } else if (error instanceof CeejayError && error.code === 'quest_daily_limit_reached') {
    const refusal = error.body as { resetsAt: string }
    console.log(`The member can complete another quest after ${refusal.resetsAt}`)
  } else {
    throw error
  }
}

ceejay.quests.reportTask

Tells CEEJAY that a member did something your own platform checks. CEEJAY completes the quest task that has your task key, and the quest when that was its last task.

POST /v1/quests/tasks/complete · requires quests:complete · API reference: Complete a quest task

Retried by the SDK: Only with idempotencyKey

The request also has a legacy endpoint, /api/quests/external/complete, which still works. The SDK uses the endpoint above.

Parameters: one object, then options.

Prop

Type

Options

Prop

Type

Identify the member with one of the six identifiers. TypeScript rejects an object that identifies nobody. If you send more than one, CEEJAY uses the first in the order of the table. The five that are not a Discord ID must match an account the member has linked in your community.

The API also accepts three of the fields under their legacy names, task_key, discord_id and x_handle, for code that calls it without the SDK. The SDK sends the names in the table.

A discordId CEEJAY has no member profile for is checked against Discord first. One that is not in your server returns no member, and the response has matched: 0.

CEEJAY completes only the tasks whose key is your taskKey. A quest that requires a role is completed only for a member who has it.

Returns the full response. It has one of two shapes, distinguished by matched.

Prop

Type

data contains:

Prop

Type

The key. A key is stored only when the call did something: at least one quest was completed or sent for review. A response with matched: 0, or with both lists empty, stores nothing, so the same request with the same key is processed as a new request, for example after the player has linked their account.

Errors

StatuscodeWhen
400bad_requestThe task key is missing or not valid, no member is identified, playerId was sent without platform, evidence is not a string, evidence or the name you sent contains a null character, or evidence contains half of a surrogate pair, such as an emoji split in two
400unknown_platformThe platform you sent with playerId is not one your community knows
403game_not_allowedThe member is named by a playerId on a platform that is a game of your community, and the key is limited to other games. Nothing happened
404not_availableThe member is identified by an account, and account linking is not available on this instance
502bot_unavailableCEEJAY's bot, which completes quests, could not be reached or rejected the request. Nothing happened, and the idempotency key is released
502bot_timeoutCEEJAY's bot did not respond in time, or its response was lost: it failed part way, or the connection to it dropped. The task may or may not have been completed
503discord_unavailableCEEJAY could not check with Discord whether the ID is a member of your server, or a matching quest requires a role and the member's roles could not be read. Nothing happened, so retry shortly

After bot_timeout. For a quest a member can complete once, retrying the task is safe. For a repeatable quest it can complete the quest again. With a key, CEEJAY does not report a second time: while the first attempt may still be running the error is idempotency_in_progress, which the SDK waits for, and after a minute it is idempotency_outcome_unknown. Idempotency errors are covered in Errors and retries.

Example

ceejay.quests.reportTask
const report = await ceejay.quests.reportTask(
  {
    taskKey: 'tk_4k2m9x7qzb',
    discordId: '409876543210987654',
    evidence: 'https://acme.example/players/nova/tutorial',
  },
  { idempotencyKey: 'tutorial-done-409876543210987654' },
)

if (report.matched === 0) {
  console.log('CEEJAY found no member for that ID')
} else {
  for (const quest of report.data.completed) {
    console.log(`Finished quest ${quest.questId}: ${quest.questTitle}`)
  }
  for (const quest of report.data.sentForReview) {
    console.log(`Quest ${quest.questId} waits for a moderator`)
  }
}

To identify the member by their ID in your game, send playerId with its platform:

ceejay.quests.reportTask by player ID
const report = await ceejay.quests.reportTask({
  taskKey: 'tk_4k2m9x7qzb',
  playerId: '88213004',
  platform: 'roblox',
})

console.log(report.matched)

When platform is a game set up for Connect, a member who connected the player themselves at the basic level is not found this way: nothing confirmed that the player is theirs, so matched is 0. A player connected at the verified level is found, and so is one your own server linked with ceejay.members.link. A game can be set up so that basic connections earn too, and then they are found.

On this page

Community StudiosPrivacyTerms