CEEJAYDocs

Complete a quest task

Report that a member finished something your own platform checks, so CEEJAY can complete the quest task that has your key.

POST /v1/quests/tasks/complete · requires quests:complete

Use this when the thing a member has to do happens on your own platform: finishing a tutorial, reaching a rank, signing up. You add a task of the type Completed by your API to a quest. When a member has done it, your server calls this request and CEEJAY completes the task. When it was the quest's last task, CEEJAY completes the quest and grants its rewards.

To complete a whole quest for a member without matching a task, use Complete a quest. That page compares the two requests.

This request is also available at a legacy endpoint, /api/quests/external/complete, which keeps working: see Complete a quest task (legacy endpoint).

Set up the task

Open Gamification → Quests. Open a quest, or press New quest, then press Add task and choose Completed by your API under Integrations.

Copy the key from the Task key field. CEEJAY fills it in as tk_ and ten lowercase letters and digits, such as tk_4k2m9x7qzb, and you can change it. Your platform sends this key to say which task a member finished. A duplicated task gets a new key. List quests does not return it, so copy it from here.

Choose whether completions go to the Review Queue with Send completions to the Review Queue instead of completing straight away. Off, which is the default, the member completes the task as soon as CEEJAY receives your request. On, a moderator approves each one first, with the evidence your platform sent.

Add a link button to the member's quest card with Where members do it (optional), and name it with Link label (optional). The card also has a Check status button, which says whether the task is done, waiting for review or rejected.

Make an API key with Quests Complete under Settings → Developers → API Keys. The task's editor lists the keys that have that scope under API key. Under What your platform calls it shows the endpoint URL and the JSON body, and Ready-made curl gives a curl command with a placeholder for the key, which Copy curl copies.

Give each task its own key, and use only letters, digits, _ and -, from 4 to 64 characters. The builder asks for one key per task but does not check it, and it accepts keys this request rejects.

Body parameters

Prop

Type

Deprecated names that still work

SendDeprecated alias, still accepted
taskKeytask_key
discordIddiscord_id
xHandlex_handle

Send one of the two. If both are sent, the deprecated one is used. Deprecated names remain supported for as long as version 1 exists, and an error message names the one you sent.

Who did it

Send one of discordId, playerId with platform, xHandle, wallet, github and twitch. If you send more than one, CEEJAY uses the first in that order and ignores the rest. A discordId that is null or an empty string is skipped, and the next one in that order is used.

FieldCEEJAY finds the member by
discordIdThe ID itself. An ID CEEJAY has no member profile for must be a member of your server: see below
playerId with platformThe account you linked with Link an account: the member's ID in your game, on that platform, ignoring capitals and spaces at its ends
xHandleThe member's connected X account: its ID or its username, ignoring capitals. A leading @ is dropped
walletThe member's connected web3 wallet: its address, ignoring capitals
githubThe member's connected GitHub account: its ID or its login
twitchThe member's connected Twitch account: its ID or its login

platform is read only when playerId is the identifier in use. With a discordId, or with no playerId, whatever the body contains under that name is ignored. With playerId, a missing platform returns 400, and a platform your community does not know returns 400 with the code unknown_platform.

When platform is a game set up for Connect and the key is limited to other games of your community, the task returns 403 with the code game_not_allowed. Otherwise, a member who connected the player themselves at the basic level is not found by playerId: nothing confirmed that the player is theirs, so the task returns 202 as it does for a player nobody is linked to. A player connected at the verified level is found, and so is one your own server linked with Link an account. A game can be set up so that basic connections earn too, and then they are found.

Send xHandle, wallet, github and twitch as strings. A whole number is read as well, as its digits, so a GitHub or Twitch ID sent as a JSON number finds the same account as that ID in quotes. That applies up to 9,007,199,254,740,991, the largest whole number a JSON number can hold with every digit intact. An X user ID is larger, so send it in quotes. A larger number, a number with a fraction, a list, an object and true or false do not match a member and return 202. They are never converted to text.

An account counts when the member connected it in your community and has not disconnected it. Members connect accounts themselves, for example through the Connect X, Connect Web3 Wallet, Connect GitHub and Connect Twitch quest tasks, or you link them with Link an account under the platform twitter, metamask, github or twitch. When two members connected the same account, the one who connected it last is used.

A discordId CEEJAY already has a member profile for is used as it is. One it has no profile for is looked up in Discord first:

  • A member of your server passes, and that result is cached for five minutes.
  • An ID that is not in your server does not match a member. The call returns 202 with matched: 0, like any identifier CEEJAY cannot match.
  • When the Discord API is unavailable, or its response does not say whether the ID is a member, the call returns 503 with the code discord_unavailable. No changes were made: retry shortly.

Returns

Prop

Type

Each entry of completed and sentForReview contains questId and questTitle. These fields are inside data. Alongside success, the response also includes the two original fields, which remain for as long as version 1 exists: matched, and completed with each quest's title under title.

Example

Request
curl -X POST https://ceejay.communitystudios.xyz/api/v1/quests/tasks/complete \
  -H "Authorization: Bearer $CEEJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskKey": "tk_4k2m9x7qzb",
    "discordId": "409876543210987654",
    "evidence": "https://acme.example/players/nova/tutorial",
    "idempotencyKey": "tutorial-88213004"
  }'

The TypeScript example uses ceejay.quests.reportTask from the TypeScript SDK. It returns the full response below.

200 OK
{
  "success": true,
  "matched": 1,
  "completed": [
    {
      "questId": 42,
      "title": "Finish the Acme tutorial"
    }
  ],
  "data": {
    "matched": 1,
    "completed": [
      {
        "questId": 42,
        "questTitle": "Finish the Acme tutorial"
      }
    ],
    "sentForReview": [],
    "duplicate": false
  }
}

When CEEJAY does not match a member, the response is 202:

202 Accepted
{
  "success": true,
  "matched": 0,
  "data": {
    "matched": 0,
    "completed": [],
    "sentForReview": []
  }
}

What a call does

  1. CEEJAY checks the key, then finds the member.
  2. It looks at the quests in your community that are active and inside their start and end dates, and at their Completed by your API tasks whose key is the one you sent. Any number of tasks can match, in any quest. A quest whose task key does not match is never started.
  3. For each match it applies the quest's own rules, listed below. A quest the member cannot take part in is skipped without an error.
  4. It completes the task. With review on, it files the report in the Review Queue instead, and data.sentForReview lists the quest.
  5. When every task of a quest is done, the quest completes, and data.completed lists it.

What CEEJAY checks, and what it does not

The quest's own rules apply. If one of them blocks a quest, the call still returns 200 with that quest omitted, and it does not say which rule applied:

  • The quest is not active, or today is outside its dates.
  • The quest requires a role the member does not have. A member who already has a run of that quest open is not checked again.
  • The member is below the level, or short of the season XP, that the quest asks for.
  • The quest belongs to a program the member has not joined.
  • The quest's prerequisite quest is not done.
  • The wait between runs of a repeatable quest has not passed.
  • The member has reached the quest's limit per member, or the quest has reached its overall limit.
  • The member has reached your community's Daily Quest Limit Per User, in your Quest settings.

When a matching quest requires a role and CEEJAY's bot cannot read the member's roles from Discord, the call returns 503 with the code discord_unavailable. Nothing was started or completed: retry shortly.

CEEJAY does not check:

  • That a member it already knows is still in your server. Only a discordId it has no member profile for is looked up in Discord.
  • That what you report is true. Unless review is on, you are confirming that the member did it.
  • That a key belongs to one task. Every live task that has the key is completed, in every quest.
  • Which API key made the call. Any key with the scope can report any task key in your community.

Review

With review on, nothing completes yet. CEEJAY files the report in the Review Queue, under Gamification → Quests, and your team gets a Quest submission needs review notification. The moderator sees evidence as the proof, or Reported done by your platform when you sent none. Approving completes the task, and the quest when it was the last task. Rejecting does not, and the next call from your platform files it again.

Review is on when the task's switch is on or the call sends review: true. A call can ask for review, but it cannot take it away. While a report is awaiting review, the quest's other automatic tasks do not advance for that member.

Without review, evidence is stored with the member's progress on the task.

Retrying safely

Send an idempotencyKey with every call you might need to retry. It is a string of 1 to 128 characters that you choose, sent in the JSON body.

  • A key is stored only when the call changed something: a 200 in which at least one quest was completed or sent for review. A retry then returns the original response, with data.duplicate: true and the header Idempotent-Replayed: true, and CEEJAY's bot is not called again.
  • No key is stored for a call that made no changes. A 202, and a 200 with both lists empty, do not use up the key. A retry with the same key is processed as a new request, for example after the player has linked the account.
  • Each key is tied to one request payload. The same request means the same task key, member, evidence and review. A key that was used for a different request returns 409 with the code idempotency_key_reused.
  • After a lost response, the key prevents a second attempt. When CEEJAY's bot does not respond in time, or its response is lost because it failed part way or the connection dropped, the call returns 502 with the code bot_timeout, and the task may or may not have been completed. With a key, the same request returns 409 with the code idempotency_in_progress for a minute, and idempotency_outcome_unknown after that: it is never sent to the bot a second time. Check the member's quests in the dashboard, then use a new key if the task has to be sent again.
  • bot_unavailable is the response that is safe to retry. It means the bot never received the task or rejected it: no changes were made, and the key is released, so a retry with the same key is processed as a new request.
  • On an instance that does not support idempotency keys yet, a call that sends one returns 503 with the code idempotency_unavailable. idempotency_key is not accepted on this endpoint; use idempotencyKey.

Without a key, what a retry does depends on the quest:

  • A quest a member can complete once: after the first completion a retry does nothing and returns 200 with empty lists. It is safe to retry.
  • A report awaiting review: a retry does nothing until a moderator decides.
  • A task already done in a quest with other tasks still open: a retry does nothing.
  • A quest members can repeat: once the wait between runs is over, a retry starts a new run and completes the task in it, and the quest grants its rewards again when that was its last task. With no wait set, every call starts a new run. So after a bot_timeout, sending the task again without a key can complete a repeatable quest a second time. Send one call for each thing the member did, with a key.

Side effects

  • When a quest completes, CEEJAY grants everything configured on it: XP, coins while your community's economy is enabled, the badge, the role and any extra rewards set on the quest in the Rewards Engine. A quest in a program also adds its XP to the member's program XP.
  • Subscribers to the quest.completed webhook get an event when the quest completes.
  • The completion is added to the member's activity history. A badge the member did not have yet can send badge.earned, and XP that levels the member up grants that level's rewards and roles and can send member.level_up.
  • A call starts the quest for the member, if they have not started it, for every live quest that has a Completed by your API task with your key. A quest that one of the rules above blocks is not started.
  • CEEJAY posts no completion message and no achievement card in Discord for this request. The member sees the task's status when they press Check status.

Errors

StatusResponseWhen
200data.matched: 1, with data.completed and data.sentForReviewThe report reached CEEJAY's bot. The lists name the quests it finished and the ones it sent for review, and are empty when it did neither
202data.matched: 0CEEJAY did not match a member. An identifier it cannot match, a discordId that is not a string of 17 to 20 digits, a discordId CEEJAY has no member profile for that is not in your server, and an identifier that is empty, null, or neither a string nor a whole number up to 9,007,199,254,740,991 all return this. No changes were made
StatuscodeerrorWhen
400bad_requesttaskKey is required (4–64 letters, digits, _ or -)taskKey is missing, is not a string, or is not 4 to 64 letters, digits, _ or -. A body that is not a JSON object returns this too
400bad_requesttask_key is required (4–64 letters, digits, _ or -)The same check, when you sent the key as task_key
400bad_requestSend one of discordId, playerId with platform, xHandle, wallet, github or twitch to say who did itThe body names nobody: none of discordId, playerId, xHandle, wallet, github and twitch is in it
400bad_requestevidence must be a stringevidence was sent and is not a string. null counts as not sent
400bad_requestevidence cannot contain a null characterevidence contains a null character (U+0000), which cannot be stored
400bad_requestevidence must be valid Unicode textevidence contains an unpaired surrogate, such as the JSON "\ud800"
400bad_requestxHandle cannot contain a null characterThe identifier the member is looked up by contains a null character. The message names the field, using the name you sent
400bad_requestplayerId must be a string of 1 to 255 charactersplayerId is not a string, or is empty once its spaces are removed
400bad_requestplayerId cannot exceed 255 charactersplayerId over 255 characters
400bad_requestplayerId cannot contain a null characterA null character in playerId
400bad_requestplatform is required with playerIdplayerId was sent without a platform
400bad_requestplatform cannot exceed 50 charactersplatform is longer than 50 characters
400bad_requestplatform cannot contain a null characterA null character in the platform
400bad_requestplatform can only contain letters, digits and underscoresplatform contains a space, a hyphen or any other character
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)
400unknown_platformUnknown platform. Add it as a custom platform under Settings, Developers, Platforms, then send the task againThe platform is not one CEEJAY knows by name, not a custom platform of your community, and one your community has no link for
403game_not_allowedThis API key is limited to other gamesThe member is named by a playerId on a platform that is a game of your community, and the key is limited to other games. Nobody is looked up and nothing is completed
404not_availableThis is not available on this instance yet.The member is named by an account, and this instance does not support account linking yet. The body includes feature
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
409idempotency_outcome_unknownAn earlier request with this idempotencyKey may or may not have been applied. It was not sent again. Use a new key to send it again.A first request with this key was started more than a minute ago and never finished. CEEJAY cannot know whether it took effect, and does not do it again. Check what happened, then use a new key if it has to be sent again
502bot_unavailableThe quest engine could not be reachedCEEJAY's bot, which completes quests, could not be reached or rejected the request. No changes were made, and the idempotency key is released
502bot_timeoutThe quest engine did not answer in time, so the task may or may not have been completedCEEJAY's bot did not respond within 12 seconds. The task may or may not have been completed, and the idempotency key stays reserved: see Retrying safely before you retry
502bot_timeoutThe quest engine did not give an answer, so the task may or may not have been completedCEEJAY's bot received the task and its response was lost: it failed part way, or the connection to it dropped. The task may or may not have been completed, and the idempotency key stays reserved: see Retrying safely before you retry
503discord_unavailableCould not check with Discord that this ID is a member of the server, so nothing was changedCEEJAY has no profile for the discordId and could not confirm with Discord whether it is a member of your server. No changes were made: retry with a pause between attempts
503discord_unavailableCould not read this member's roles from Discord, so no quest was completedA matching quest requires a role, and the member's roles could not be read from Discord. Nothing was started or completed: 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

A body that is not valid JSON, or is over 1 MB, is rejected before the key is checked.

CEEJAY returns 202, not an error, when it does not match a member, and the response says nothing else about the member. This is deliberate, so that nobody can work out who is in your community by trying names. It is not a full guarantee: 0 against 1 still shows whether an X handle, wallet or login is connected, and whether a Discord ID is one CEEJAY knows or a member of your server.

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.

Send Discord IDs as strings

A JSON number cannot represent a Discord ID accurately: its last digits change, and it would identify someone else. CEEJAY never converts a number into an ID, so a discordId sent as a JSON number does not match a member and returns 202, even when you sent another identifier alongside it. Always send it as a string.

On this page

Community StudiosPrivacyTerms