Luk Quest API
Luk Quest keeps your users’ quests, progress and rewards, and your site reads them through one API link per project. Your server reports what users do as events and reads back each user’s state; an optional browser snippet does the same for the signed-in user. Every example uses qs_YOUR_SECRET_KEY and your project’s API link from Integration.
Concepts
A company has projects; each project is one website or app with its own API link, keys, players and content.
| Word | Means |
|---|---|
| Project | One website or app. Owns the API link, secret keys, players, metrics, tasks, rewards and quests. |
| API link | https://<host>/api/v1/p/<project key>. Every call goes to it. |
| Player | One of your users, known by your own user id (any string up to 191 characters). Created on first contact. |
| Traits | Facts about a player you send with identify (plan, country…). Quest audiences match on them. |
| Metric | A kind of fact your site reports, e.g. purchase.amount: a value type, a unit, and how values add up. |
| Event | One report of a metric for one player: player, metric, value, and optionally your id, a target and attributes. |
| Task | A step players see (title, description, button), done when one metric reaches a target. |
| Condition | A rule with no player copy, deciding who may join a quest (e.g. finished another quest). |
| Quest | A circuit: Who → Appearance → When → What (tasks) → Completion → Rewards. Referred to by its key. |
| Version | A frozen publication of a quest. A player stays on the version they joined. |
| Points | A currency (XP, coins…), kept in a ledger. A project can have several. |
| Badge | A collectible earned once. |
| Perk | A named entitlement your site checks (free-shipping), optionally for N days. |
| Award rule | Points straight from events, e.g. 1 XP per dollar, at most 500 a day. |
Metrics and tasks
Metrics are data: your site reports purchase.count went up by 1. Tasks are what players see: “Make your first purchase — Browse the shop. [Go to shop]”, done when purchase.count reaches 1 since the player joined the quest. One metric can feed many tasks; one task can be used in many quests.
How a quest decides
- Who — Which players the quest is for: everyone, or players with given ids or traits your site sends with identify.
- Appearance — Who may join and how: conditions to join (like having finished another quest), and whether players join automatically or by pressing Start. Players who don’t meet the conditions see the quest locked (or not at all, see Visibility).
- When — When the quest is open: one window, chosen days of the week, a weekly window (e.g. Friday to Sunday), or repeating rounds. Outside it, nobody joins and progress stops. Each round of a recurring quest starts over, and its leaderboard settles when the round ends.
- What — The tasks players see. All in any order by default; switch to Any, At least N or One by one.
- Completion — How players finish (automatically, or by confirming), the Complete button and the message, and what to offer next. How they join is set on Appearance.
- Rewards — What finishing pays: points, badges and perks, to every finisher, the first N, or leaderboard places.
Quickstart
Three calls from your server cover most sites: identify players, send events, read state. Store the secret key in your server’s environment (e.g. LUKQUEST_SECRET_KEY).
1. Tell Luk Quest who the player is
curl -X PUT "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"displayName":"Ada","traits":{"plan":"pro","country":"PL"}}'2. Send events when players do things
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/events" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"player":"user-42","metric":"purchase","id":"order-1001"}'3. Read the player’s quests and rewards
curl "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"Authentication
Every call goes to your project’s API link, https://<host>/api/v1/p/<project key>. The project key (qp_…) identifies your company and project; it isn’t a secret.
From your server, send a secret key: Authorization: Bearer qs_…. Secret keys are made under Integration (owners and managers), shown once, and can be revoked. A secret key sent from a browser (a request with an Origin header) is refused with secret_in_browser.
From a browser or app, send a player token: Authorization: Player pt_…. Your server gets one with POST /player-tokens for the signed-in user; it lasts an hour by default (ttlSeconds up to 7 days) and lets the browser act only for that player, through the /me calls.
Browser calls must come from one of the project’s allowed origins (Integration → Allowed origins), e.g. https://shop.example.com. Preflight requests from other origins are refused.
curl "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"GET <API link> answers { ok, project, auth }: use it to check a key or a token.
Players
PUT/players/:playerIdsecret key
Creates the player if new and merges traits into what’s stored (null removes a trait). Values may be text, numbers, booleans or lists. Send it after sign-up and whenever traits change: audiences match on them. The answer is the player’s state, plus changes (quests they joined or finished as a result).
curl -X PUT "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"displayName":"Ada","traits":{"plan":"pro","country":"PL"}}'GET/players/:playerIdsecret key
GET/meplayer token
Everything your site needs to show, evaluated now: the player, points balances, badges, active perks and every quest they can see, with its state, tasks (copy, status, progress) and rewards. Unknown players answer as new (known: false) without being created.
{
"player": {
"id": "user-42",
"displayName": "Ada",
"traits": {
"plan": "pro",
"country": "PL"
},
"firstSeenAt": "2026-10-01T09:12:00.000Z",
"lastSeenAt": "2026-10-12T10:30:00.000Z",
"known": true
},
"points": [
{
"key": "xp",
"title": "XP",
"symbol": "XP",
"decimals": 0,
"balance": "142"
},
{
"key": "coins",
"title": "Coins",
"symbol": "◎",
"decimals": 0,
"balance": "5"
}
],
"badges": [
{
"key": "welcome",
"title": "Welcome aboard",
"description": "Finished the welcome quest.",
"imageUrl": "",
"rarity": "common",
"awardedAt": "2026-10-12T10:30:00.000Z"
}
],
"perks": [
{
"key": "free-shipping",
"title": "Free shipping",
"description": "Free delivery on every order.",
"grantedAt": "2026-10-12T10:30:00.000Z",
"expiresAt": "2026-11-11T10:30:00.000Z"
}
],
"quests": [
{
"key": "big-spender",
"title": "Big spender",
"description": "Spend $100 for a badge and a month of free shipping.",
"imageUrl": "",
"category": "shopping",
"design": null,
"state": "active",
"joining": "player",
"completion": "player",
"startsAt": null,
"endsAt": null,
"joinedAt": "2026-10-12T10:31:00.000Z",
"completedAt": null,
"progress": {
"done": 0,
"total": 1
},
"tasks": {
"mode": "all",
"required": 1,
"items": [
{
"type": "task",
"key": "spend-100",
"title": "Spend $100",
"description": "Orders since you joined this quest add up.",
"action": {
"label": "Go to shop",
"url": "/shop"
},
"imageUrl": "",
"status": "in_progress",
"progress": {
"current": "42.5",
"target": "100",
"unit": "USD",
"percent": 42
}
}
]
},
"rewards": [
{
"type": "badge",
"key": "big-spender",
"title": "Big spender",
"description": "Spent $100 in the shop.",
"imageUrl": "",
"rarity": "rare",
"rule": "every"
},
{
"type": "perk",
"key": "free-shipping",
"title": "Free shipping",
"description": "Free delivery on every order.",
"days": 30,
"rule": "every"
},
{
"type": "points",
"key": "coins",
"title": "Coins",
"symbol": "◎",
"amount": "50",
"description": "",
"rule": "first_n",
"limit": 100
}
],
"buttons": {
"start": "Take the challenge",
"complete": "Claim rewards"
},
"congratsText": "You’re a big spender! Enjoy free shipping for 30 days.",
"next": null,
"version": 1
}
],
"asOf": "2026-10-12T10:32:00.000Z"
}GET/players/:playerId/ledger?cursor=secret key
The history of points: every entry with its amount, kind (rule, quest, leaderboard, manual, correction, revoked) and time, newest first, 50 at a time.
Events
POST/eventssecret key
POST/me/eventsplayer token
One event per fact. The metric must exist in the project (Metrics). Count metrics default to a value of 1. Each event is evaluated at once: award rules pay points, and the player’s quests move on (joined, ready, completed, with rewards).
| Field | Means |
|---|---|
| player | Your user id. Required. |
| metric | The metric key. Required. |
| value | A number (JSON number or decimal string like "42.50") or, for text metrics, a string. Negative only in corrections. |
| id | Your own id for the fact, e.g. an order id. Same metric + id = the same event: retries are recognised as duplicates. |
| target | What it was about (a product, article or quest id). Tasks and rules can count only one target; distinct metrics count different targets. |
| attributes | Up to 20 flat values (text, number, boolean) tasks can filter on, e.g. { "category": "shoes" }. |
| occurredAt | When it happened (ISO 8601). Defaults to now; at most a year ago and not in the future. |
| correctionOf | The id of an earlier event this one corrects (a refund). It may be negative, and takes back what the original earned. |
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/events" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"player":"user-42","metric":"purchase.amount","value":42.5,"id":"order-1001"}'To send several at once, wrap them: { "events": [ … ] } (up to 100). Each is recorded on its own, so one bad event never sinks the others.
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/events" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[{"player":"user-42","metric":"purchase","id":"order-1001"},{"player":"user-42","metric":"purchase.amount","value":"42.50","id":"order-1001"}]}'The answer says, for each event, accepted, duplicate (with the first outcome) or rejected (with code and error), and what it did: points from award rules and quests that changed state. A single event that’s rejected answers with an error status instead.
{
"ok": true,
"accepted": 1,
"duplicate": 0,
"rejected": 0,
"results": [
{
"index": 0,
"status": "accepted",
"eventId": 9182,
"points": [
{
"rule": "xp-per-dollar",
"points": "xp",
"amount": "42"
}
],
"quests": [
{
"quest": "big-spender",
"from": "active",
"to": "ready"
}
]
}
]
}Always send id for facts that must count once (orders, payments): a network retry with the same id and content answers duplicate; the same id with different content is refused with event_conflict.
Refunds and cancellations are new events with correctionOf: a negative value lowers sums, a count goes down by one, and points paid by award rules for the original are taken back (never more than it earned).
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/events" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"player":"user-42","metric":"purchase.amount","value":"-42.50","id":"refund-1001","correctionOf":"order-1001"}'Quests
GET/questssecret key or player token
The published quests (not hidden ones), without a player: titles, tasks, rewards and windows, e.g. for a landing page.
GET/players/:playerId/quests/:questKeysecret key
POST/players/:playerId/quests/:questKey/startsecret key
POST/players/:playerId/quests/:questKey/completesecret key
POST/me/quests/:questKey/start · /completeplayer token
Quests where players press Start need a start call (it refuses with quest_locked, quest_not_open or quest_ended). Quests that complete when the player confirms wait in ready until a complete call (quest_not_ready if tasks remain). Automatic quests need neither. Both answer the quest’s new state.
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42/quests/big-spender/start" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42/quests/big-spender/complete" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"Quest states
| State | Means |
|---|---|
| available | The player can join (press Start). |
| active | Joined; progress counts. |
| ready | Tasks done; waiting for the player to confirm. |
| completed | Finished; rewards paid. |
| locked | Visible, but its conditions aren’t met yet (see `locks`). |
| upcoming | Its window hasn’t opened yet. |
| expired | The window closed before the player finished. |
| closed | The quest was archived; earned rewards stay. |
Task states
| State | Means |
|---|---|
| todo | Not started. |
| in_progress | Some progress (see `progress`). |
| done | Done. |
| locked | Waits for the previous step (one by one). |
When can be one window (dates), chosen days of the week, or repeating rounds (every N days or weeks). A recurring quest answers schedule (mode, and days/from/until or every/unit) and round ({ key, startsAt, endsAt }, null between rounds); startsAt/endsAt are the current round’s, or the next one’s while upcoming. Each round is a fresh run: progress starts over, the quest can be completed once per round and rewards pay each round.
Publishing a quest makes a frozen version. Players who already joined keep their version until they finish; everyone else gets the newest. version in the answer says which one a player is on.
Rewards
Rewards come with the player’s state: points (balances per currency, with decimals), badges (once each) and perks (active ones, with expiresAt, or null for no end). Inside each quest, rewards lists what finishing pays and to whom (rule: every, first_n with limit, leaderboard with places).
Award rules pay points straight from events; the event answer lists them under points, with capped when a cap applied.
Check a perk by its key in perks (e.g. free-shipping) to unlock the feature on your site; it disappears from the state when it expires or is revoked.
Leaderboards
GET/quests/:questKey/leaderboard?player=&limit=secret key or player token
A quest’s leaderboard (/quests/:questKey/leaderboard) ranks its finishers by finishing time or by a metric total inside the quest’s window (each round, for a recurring quest); while open it’s provisional, and after the window ends it’s settled once and places are paid. Pass player to get their own entry in you. Browsers see display names only, never other players’ ids. (/leaderboards/:questKey from before built leaderboards still answers, marked with a Deprecation header; move to the quest path.)
curl "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/quests/spend-off/leaderboard?player=user-42" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"{
"quest": {
"key": "spend-off",
"title": "October spend-off"
},
"rankBy": "purchase.amount",
"state": "provisional",
"endsAt": "2026-10-31T23:00:00.000Z",
"settledAt": null,
"total": 84,
"entries": [
{
"place": 1,
"player": "user-7",
"displayName": "Kit",
"score": "820",
"completedAt": "2026-10-03T12:00:00.000Z",
"prize": {
"type": "points",
"key": "xp",
"amount": "1000"
}
},
{
"place": 2,
"player": "user-42",
"displayName": "Ada",
"score": "640",
"completedAt": "2026-10-02T08:00:00.000Z",
"prize": {
"type": "points",
"key": "xp",
"amount": "250"
}
}
],
"you": {
"place": 2,
"player": "user-42",
"displayName": "Ada",
"score": "640",
"completedAt": "2026-10-02T08:00:00.000Z",
"prize": {
"type": "points",
"key": "xp",
"amount": "250"
},
"you": true
}
}GET/leaderboards/:key?player=&league=&limit=secret key or player token
Built leaderboards (/leaderboards/:key) rank their members by a score (metric totals, points earned, badges earned). They answer leaderboard (its period, nextPayoutAt, leagues: none, global or seasonal), tiers (the leagues or groups with their sizes), the listed entries of one league (the player’s own, or league=), and you: place, score, tier, and on global leagues danger (lastPlaces, overtaken) and canPromote. A player who joined during a season reads waiting: true until the next one.
curl "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/leaderboards/spenders-ladder?player=user-42" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"{
"leaderboard": {
"key": "spenders-ladder",
"title": "Spenders’ ladder",
"description": "",
"type": "permanent",
"leagues": "global",
"switching": "player",
"joining": "automatic",
"joinButtonTitle": "",
"period": {
"key": "2026-10-11T18:00:00.000Z",
"startsAt": "2026-10-11T18:00:00.000Z",
"endsAt": "2026-10-18T18:00:00.000Z",
"state": "open"
},
"nextPayoutAt": "2026-10-18T18:00:00.000Z"
},
"tiers": [
{
"index": 0,
"name": "Gold",
"size": 100,
"count": 100
},
{
"index": 1,
"name": "Silver",
"size": 100,
"count": 100
},
{
"index": 2,
"name": "Bronze",
"size": null,
"count": 412
}
],
"tier": {
"index": 1,
"name": "Silver",
"size": 100,
"count": 100
},
"members": 612,
"entries": [
{
"place": 1,
"player": "user-9",
"displayName": "Noor",
"score": "980",
"danger": {
"lastPlaces": false,
"overtaken": false
}
},
{
"place": 2,
"player": "user-42",
"displayName": "Ada",
"score": "955",
"danger": {
"lastPlaces": false,
"overtaken": false
},
"you": true,
"canPromote": true
}
],
"you": {
"place": 2,
"player": "user-42",
"displayName": "Ada",
"score": "955",
"danger": {
"lastPlaces": false,
"overtaken": false
},
"you": true,
"canPromote": true,
"tier": {
"index": 1,
"name": "Silver",
"size": 100,
"count": 100
},
"joined": true
}
}POST/players/:playerId/leaderboards/:key/join · /promotesecret key
POST/me/leaderboards/:key/join · /promoteplayer token
Leaderboards where players press Join need a join call (leaderboard_locked when Who or Appearance doesn’t match). On global leagues where players decide, promote moves the player up one league when it has room or when they beat its lowest score; that player moves down. Otherwise it answers promotion_not_allowed.
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42/leaderboards/weekly-league/join" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/players/user-42/leaderboards/spenders-ladder/promote" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY"Player state (GET /players/:id, /me) lists the player’s leaderboards in leaderboards, and ones they can join (you.joined: false).
Browser snippet
The snippet is a small script with no dependencies. It keeps the signed-in player’s state, refreshes it after every action, and tells you when it changes. Calls are queued until the script loads, like Google Analytics. Metrics must allow browser events.
1. Your server asks for a player token
curl -X POST "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY/player-tokens" \
-H "Authorization: Bearer qs_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"player":"user-42","ttlSeconds":3600}'2. Paste the snippet into your pages
<!-- Luk Quest: in <head>, on every page -->
<script async src="https://YOUR-LUKQUEST-HOST/sdk/v1.js"></script>
<script>
window.lukquest = window.lukquest || function () { (lukquest.q = lukquest.q || []).push(arguments); };
lukquest("init", "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY");
// After sign-in: a player token your server got from POST /player-tokens
lukquest("identify", "PLAYER_TOKEN_FROM_YOUR_SERVER");
</script>3. Use it
// Anywhere after the snippet:
lukquest("track", "article.read", { target: "article-123" }); // only metrics that accept browser events
lukquest("on", "update", (state) => {
// state = the same answer as GET /players/:id: quests, tasks, progress, points, badges, perks
renderQuests(state.quests);
});
const quest = await lukquest.start("welcome"); // a quest players start themselves
const done = await lukquest.complete("welcome"); // a quest players confirm
const board = await lukquest.leaderboard("weekly-league"); // a built leaderboard
const finishers = await lukquest.questLeaderboard("spend-off"); // a quest’s leaderboard
await lukquest.joinLeaderboard("weekly-league"); // leaderboards players join themselves
await lukquest.promote("weekly-league"); // move up a league (when players decide)| Call | Means |
|---|---|
| lukquest("init", apiLink) | Sets your project’s API link. |
| lukquest("identify", token | () => Promise<token>) | Sets the player token from your server; pass a function to fetch a fresh one when it expires. |
| lukquest("track", metric, { value, target, attributes, id }) | Sends an event for the player (browser metrics only). |
| lukquest("on", "update" | "error", handler) | update: the player’s state after every change; error: a failed call. |
| lukquest.me() | Answers the player’s state (same as GET /players/:id). |
| lukquest.start(key) · lukquest.complete(key) | Start or complete a quest; answer the quest. |
| lukquest.leaderboard(key, { league }) | Answers a built leaderboard with the player’s own entry; { league } picks another league. |
| lukquest.questLeaderboard(questKey) | Answers a quest’s leaderboard with the player’s own entry. |
| lukquest.joinLeaderboard(key) | Joins a leaderboard where players press Join; answers the player’s place. |
| lukquest.promote(key) | Moves the player up one league (global leagues where players decide). |
| lukquest.refresh() | Reloads the state and emits update. |
Widgets
POST/widget/bootallowed origin + userHash
Widgets are ready-made rewards UI made under Widgets in the studio: a launcher (a floating button that opens a popover, a sheet on phones), an inline box, or a full page. They come with the browser snippet: the same script, booted with a signed user id instead of a token. Floating launchers appear by themselves; boxes, pages and inline launchers (a button in your header, say, with the popover opening from it) render into <div data-lukquest-widget="key"></div>, also when it’s added later.
<!-- Luk Quest widgets: in <head>, on every page -->
<script async src="https://YOUR-LUKQUEST-HOST/sdk/v1.js"></script>
<script>
window.lukquest = window.lukquest || function () { (lukquest.q = lukquest.q || []).push(arguments); };
lukquest("init", "https://YOUR-LUKQUEST-HOST/api/v1/p/qp_YOUR_PROJECT_KEY");
// After sign-in. USER_HASH comes from your server (see “Sign the user id”).
lukquest("boot", { userId: "USER_ID", userHash: "USER_HASH" });
</script>Boot from an allowed origin with your user id and userHash: the hex HMAC-SHA256 of the user id with the project’s identity secret (Widgets → Identity), computed on your server. Boot answers a player token (refreshed by the script) and the enabled widgets. Already minting player tokens? Boot with { token } instead. Call shutdown on sign-out.
import crypto from "node:crypto";
const userHash = crypto.createHmac("sha256", process.env.LUKQUEST_IDENTITY_SECRET)
.update(String(user.id)).digest("hex");| Call | Means |
|---|---|
| lukquest("boot", { userId, userHash } | { token }) | Signs the user in and shows the enabled widgets. |
| lukquest("shutdown") | Removes the widgets and forgets the user. |
| lukquest.widgets() | Answers the enabled widgets. |
| lukquest("open" | "close" | "toggle", key?) | Opens, closes or toggles a launcher’s popover (the first launcher without a key). |
| lukquest("show" | "hide", key?) | Shows or hides launchers (one by key, or all). |
| lukquest("mount", key, element) · lukquest("unmount", key) | Renders a box, page or inline launcher into an element; unmount removes it. |
| lukquest("on", "open" | "close", handler) | open, close: a launcher’s popover; update and error as above. |
AI assistants (MCP)
POST/api/mcp/:tokenconnection link
Each project can have up to 10 connection links (Integration → AI assistants, or “Connect your AI” on any project page). A link is an MCP server over Streamable HTTP (stateless, POST only) at https://<host>/api/mcp/<token>; clients that send headers may use /api/mcp with Authorization: Bearer <token>. Links are read only or read & write, work in their project only, act as the member who made them, and can be revoked at once. Only the token’s fingerprint is stored.
Read tools: overview, get_library, get_spec_format, get_quest, validate_quest, simulate_quest, quest_stats, get_leaderboard, validate_leaderboard, leaderboard_standings, list_players, get_player, list_events. Read & write links add create_library_item, update_library_item, archive_library_item, create_quest, update_quest_draft, publish_quest, archive_quest, create_leaderboard, update_leaderboard_draft, publish_leaderboard, archive_leaderboard and settle_leaderboard. Quests and leaderboards can be described with a simple spec instead of a graph. Nothing can be deleted, and player commands stay in the studio.
Errors
Failures answer { ok: false, error, code } with an HTTP status. error is a sentence for people; code is stable for your code. Every call, failed or not, is listed under Events → API calls with its reason.
{
"ok": false,
"error": "There’s no metric “purchse” in this project. Create it under Metrics first.",
"code": "metric_unknown"
}| Status | Code | Means |
|---|---|---|
| 400 | metric_unknown | No metric with that key in this project. |
| 400 | value_invalid | The value isn’t a number (or a whole number for counts). |
| 400 | value_negative | Negative values need correctionOf. |
| 400 | player_required | The player id is missing. |
| 400 | correction_unknown | No earlier event of this player and metric has that id. |
| 400 | traits_invalid | Traits must be an object of simple values. |
| 401 | key_required | No secret key or player token. |
| 401 | key_invalid | The secret key is wrong or revoked. |
| 401 | player_token_invalid | The player token is wrong or expired. |
| 401 | identity_invalid | The userHash is missing or doesn’t match the user id (or the identity secret was rotated). |
| 403 | needs_secret | This call needs a secret key. |
| 403 | needs_player_token | This call needs a player token. |
| 403 | secret_in_browser | A secret key was sent from a browser. |
| 403 | origin_not_allowed | The browser’s origin isn’t allowed. |
| 403 | metric_not_browser | This metric doesn’t accept browser events. |
| 403 | metric_reserved | quest.completed is reported by Luk Quest. |
| 403 | project_archived | The project is archived. |
| 404 | project_unknown | No project has that key. |
| 404 | quest_not_found | No open quest with that key. |
| 404 | quest_not_available | The player can’t see that quest. |
| 404 | leaderboard_not_found | No leaderboard (or quest) has that key, or it isn’t published. |
| 404 | widget_unknown | The browser script has no enabled widget with that key. |
| 409 | event_conflict | Same id, different content. |
| 409 | quest_locked | Its conditions aren’t met. |
| 409 | quest_not_open | Its window hasn’t opened. |
| 409 | quest_ended | Its window has ended. |
| 409 | quest_not_joined | The player hasn’t joined. |
| 409 | quest_not_ready | Tasks remain. |
| 409 | quest_completed | Already completed. |
| 409 | leaderboard_locked | The player doesn’t match the leaderboard’s Who or Appearance yet. |
| 409 | leaderboard_closed | The leaderboard is archived or its seasons have ended. |
| 409 | promotion_not_allowed | The player can’t move up now (top league, not a member, or no room and no lower score to beat). |
| 429 | rate_limited | Too many calls this minute; see Retry-After. |
Limits
- 1,200 calls a minute per project by default (429 rate_limited with Retry-After).
- Up to 100 events per call from a server.
- Up to 20 events per call from a browser.
- Player ids, event ids and targets up to 191 characters; metric and quest keys up to 64.
- Up to 20 attributes per event and 50 traits per player.
- occurredAt at most a year ago and at most 5 minutes in the future.
- Player tokens last 1 hour by default, at most 7 days.
- The API call log keeps 30 days.