Files
tekton/server/nakama/lua/README.md
T

53 KiB

Tekton Nakama Lua Server API Reference

Auto-generated from source code. For AI agents -- call these RPCs without reading Lua.

Table of Contents

↑ Back to top

  1. Authentication & Core (core.lua)
  2. Utilities (utils.lua)
  3. User Module (user.lua)
  4. Economy Module (economy.lua)
  5. Gacha Module (gacha.lua)
  6. Leaderboard Module (leaderboard.lua)
  7. Inbox/Mail Module (inbox.lua)
  8. Daily Rewards Module (daily_rewards.lua)
  9. Admin Module (admin.lua)
  10. Storage Collections Reference
  11. Leaderboard Config
  12. Error Strings Reference
  13. Permission Levels

1. Authentication & Core (core.lua)

↑ Back to top

After-Hook: after_authenticate_steam

↑ Back to top

Field Value
Trigger AuthenticateSteam
Purpose On first Steam login: sets display_name from Steam username, default role to "player"
Auth None (internal hook)

Behavior:

  • If user has no display_name, sets it from input.username (fallback: "SteamPlayer")
  • If user metadata has no role, sets metadata.role = "player"

Startup (not an RPC)

↑ Back to top

nk.leaderboard_create("global_high_score", true, "desc", "best", nil, {})

Creates native leaderboard "global_high_score" on module load (also created in leaderboard.lua -- pcall wraps both so duplicate is safe).


2. Utilities (utils.lua)

↑ Back to top

Shared helpers used by other modules.

Constants

↑ Back to top

Constant Value Purpose
ADMIN_ROLES {admin=true, moderator=true, owner=true} Roles with admin privileges
SYSTEM_USER_ID "00000000-0000-0000-0000-000000000000" System/nil user for global storage
CHANNEL_TYPE_ROOM 1 Nakama channel type for rooms
CHANNEL_TYPE_DIRECT 2 Nakama channel type for DMs
CHANNEL_TYPE_GROUP 3 Nakama channel type for groups

utils.is_admin(context)

↑ Back to top

Checks if caller has admin/moderator/owner role.

Param Type Description
context table Nakama RPC context

Returns: boolean

utils.is_match_host(context, match_id)

↑ Back to top

Checks if caller is the host of a match.

Param Type Description
context table Nakama RPC context
match_id string Match ID to check

Returns: boolean Note: Reads match.state and checks state.hostUserId against context.user_id.

utils.require_admin(context)

↑ Back to top

Errors with "Admin privileges required" if caller is not admin/moderator/owner.

utils.require_admin_or_host(context, match_id)

↑ Back to top

Errors with "Admin or host privileges required" if caller is neither admin nor match host.

utils.resolve_channel_id(channel_id)

↑ Back to top

Resolves a friendly room name (e.g. "social_global") to a hashed Nakama channel ID.

Param Type Description
channel_id string Raw channel ID or friendly room name

Returns: string -- resolved channel ID (passes through if already hashed, i.e. contains ".") Logic: If no "." in value, calls nk.channel_id_build("", name, CHANNEL_TYPE_ROOM).


3. User Module (user.lua)

↑ Back to top

RPC: get_user_profile

↑ Back to top

Field Value
Function user.rpc_get_user_profile
Auth None required (can view any profile)
Purpose Get a user's public profile

Params (JSON):

Field Type Required Default Description
user_id string No context.user_id Target user ID

Returns (JSON):

Field Type Description
user_id string User ID
username string Username
display_name string Display name
avatar_url string Avatar URL
create_time string Account creation time
role string Role from metadata ("player" default)

Errors:

  • "Account not found" -- user doesn't exist
  • "Account banned until <time>. Reason: <reason>" -- temporary ban still active
  • "Account permanently banned. Reason: <reason>" -- permanent ban

Ban logic: Reads metadata.banned, metadata.ban_expires (Unix timestamp), metadata.ban_reason. If ban_expires <= os.time(), auto-clears the ban on read.

RPC: update_user_profile

↑ Back to top

Field Value
Function user.rpc_update_user_profile
Auth Required
Purpose Update own display name and/or avatar

Params (JSON):

Field Type Required Description
display_name string No New display name
avatar_url string No New avatar URL

Returns (JSON):

Field Type Description
success bool Always true

Errors:

  • "Not authenticated"
  • "Failed to update profile"

RPC: search_users

↑ Back to top

Field Value
Function user.rpc_search_users
Auth Required
Purpose Search users by username or display name

Params (JSON):

Field Type Required Default Description
query string No "" Search term (ILike match on username/display_name). Empty = list all (limit 100).

Returns (JSON):

Field Type Description
users array Array of user objects

User object:

Field Type Description
user_id string User ID
username string Username
display_name string Display name
avatar_url string Avatar URL from metadata

SQL: Queries users table with ILIKE. Excludes system user 00000000-0000-0000-0000-000000000000. Limit 100.

RPC: change_credentials

↑ Back to top

Field Value
Function user.rpc_change_credentials
Auth Required
Purpose Change email + password credentials

Params (JSON):

Field Type Required Description
new_email string No New email
new_password string No New password
current_password string If email set Current password (required to unlink old email)

Returns (JSON):

Field Type Description
success bool Always true

Errors:

  • "Not authenticated"
  • "Current password required" -- account has email but no current_password provided
  • "Incorrect current password." -- email auth re-verify failed
  • "Failed to set new credentials: <err>" -- link_email failed (rolls back old link)

Logic: Verifies current password via nk.authenticate_email, unlinks old email, links new email+password. On failure, re-links old credentials.

RPC: send_lobby_invite

↑ Back to top

Field Value
Function user.rpc_send_lobby_invite
Auth Required
Purpose Send a lobby invitation notification

Params (JSON):

Field Type Required Description
to_user_id string Yes Recipient user ID
match_id string Yes Match/lobby ID to invite to

Returns (JSON):

Field Type Description
success bool Always true

Errors:

  • "Not authenticated"
  • "Missing to_user_id or match_id"

Notification: Code 1001, persistent (true). Content: {match_id, from_name}.

RPC: send_friend_request

↑ Back to top

Field Value
Function user.rpc_send_friend_request
Auth Required
Purpose Send a friend request notification

Params (JSON):

Field Type Required Description
user_id string Yes Target user ID

Returns (JSON):

Field Type Description
success bool Always true

Errors:

  • "Not authenticated"
  • "user_id is required"
  • "Cannot add yourself"

Notification: Code 1002, persistent (true). Content: {from_user_id, from_name}.

RPC: admin_get_user_history

↑ Back to top

Field Value
Function user.rpc_admin_get_user_history
Auth Admin required
Purpose Get wallet ledger, login history, and match history for a user

Params (JSON):

Field Type Required Description
user_id string Yes Target user ID

Returns (JSON):

Field Type Description
history.wallet_ledger array Last 50 wallet ledger entries
history.logins array Login history (up to 20)
history.matches array Match history from storage collection "matches"

Errors:

  • "Admin privileges required" (via utils.require_admin)
  • "user_id is required"

After-Hook: after_authenticate

↑ Back to top

Field Value
Trigger AuthenticateDevice, AuthenticateEmail, AuthenticateCustom
Purpose Record login timestamp + IP to user storage

Storage write: Collection "history", key "logins", permission_read=0, permission_write=0. Keeps last 20 entries. Each entry: {time, ip}.


4. Economy Module (economy.lua)

↑ Back to top

Shop Catalog

↑ Back to top

Hardcoded items available for purchase:

ID Name Category Gold Star Rarity Character
oldpop-blue-hat Oldpop Blue Hat head 100 0 Common Oldpop
oldpop-green-hat Oldpop Green Hat head 100 0 Common Oldpop
oldpop-red-hat Oldpop Red Hat head 100 0 Common Oldpop
oldpop-yellow-hat Oldpop Yellow Hat head 100 0 Common Oldpop
oldpop-og-pant Copper OG Pant costume 0 0 Common Oldpop
oldpop-grey-pant Copper Grey Pant costume 150 0 Common Oldpop
oldpop-red-pant Copper Red Pant costume 150 0 Common Oldpop
oldpop-yellow-pant Copper Yellow Pant costume 150 0 Common Oldpop
oldpop-blue-gloves Oldpop Blue Gloves glove 75 0 Common Oldpop
oldpop-green-gloves Oldpop Green Gloves glove 75 0 Common Oldpop
oldpop-red-gloves Oldpop Red Gloves glove 75 0 Common Oldpop
oldpop-yellow-gloves Oldpop Yellow Gloves glove 75 0 Common Oldpop

RPC: get_shop_catalog

↑ Back to top

Field Value
Function economy.rpc_get_shop_catalog
Auth Required
Purpose Get the shop catalog grouped by category, plus featured banners

Params: None (ignore payload)

Returns (JSON):

Field Type Description
catalog object Grouped by category (head, costume, glove). Each entry: {id, name, gold, star, rarity, character}
featured_banners array Banners from shop_config/featured_banners (max 3 items, each: {item_id, ...})

Errors: "Not authenticated"

RPC: buy_currency

↑ Back to top

Field Value
Function economy.rpc_buy_currency
Auth Required
Purpose Purchase a currency package (IAP)

Params (JSON):

Field Type Required Description
package_id string Yes One of: gold_100, gold_500, gold_1000, gold_2000, gold_5000, gold_10000, star_100, star_250, star_600
receipt string For gold packages Store receipt for validation
idempotency_key string Yes Idempotency key (prevents double-claim)
store_type string No "google", "apple", or "test" (default: "test")

Package table:

Package ID Gold Change Star Change Requires Verification
gold_100 +100 0 Yes
gold_500 +550 0 Yes
gold_1000 +1150 0 Yes
gold_2000 +2400 0 Yes
gold_5000 +6250 0 Yes
gold_10000 +13000 0 Yes
star_100 -500 +100 No
star_250 -1100 +250 No
star_600 -2500 +600 No

Returns (JSON) -- success:

Field Type Description
success bool true
status string "verified" (receipt validated) or "pending" (no receipt, awaiting verification)
package_id string Package ID
duplicate bool (optional) true if idempotency key already used

Errors:

  • "Not authenticated"
  • "Package ID required"
  • "Idempotency key required"
  • "Invalid package ID"
  • "InvalidReceipt" -- IAP validation failed
  • "NotEnoughFunds" -- wallet update failed (e.g. insufficient gold for star packages)

Idempotency: Checks receipts collection with idempotency_key. Returns existing result on duplicate.

Receipt validation: For store_type="test", accepts receipt == "mock_receipt_for_now".

RPC: purchase_item

↑ Back to top

Field Value
Function economy.rpc_purchase_item
Auth Required
Purpose Purchase an item from the shop catalog

Params (JSON):

Field Type Required Default Description
item_id string Yes -- Item ID from catalog
quantity number No 1 Quantity (min 1)
idempotency_key string Yes -- Idempotency key

Returns (JSON):

Field Type Description
success bool true
item string Item ID purchased
duplicate bool (optional) true if idempotency key already used

Errors:

  • "Not authenticated"
  • "Item ID required"
  • "Invalid quantity"
  • "Idempotency key required"
  • "ItemNotFound"
  • "NotEnoughFunds" -- insufficient gold/star balance
  • "PurchaseFailed" -- storage write failed

Storage writes:

  • Collection "inventory", key item_id: {category, purchased_at, quantity}
  • Collection "receipts", key idempotencyKey: {type="item", item_id, quantity, cost, processed_at}

↑ Back to top

Field Value
Function economy.rpc_admin_set_featured_banners
Auth Admin required
Purpose Set up to 3 featured banner items for the shop

Params (JSON):

Field Type Required Default Description
banners array No [] Array of banner objects, each with {item_id, ...} (max 3)

Returns (JSON):

Field Type Description
success bool true
banners array Final banners array (max 3)

Errors:

  • "Admin privileges required"
  • "Item not found in catalog: <itemId>" -- banner references a non-catalog item

Storage: Collection "shop_config", key "featured_banners", user SYSTEM_USER_ID, permission_read=2, permission_write=0.

↑ Back to top

Field Value
Function economy.rpc_admin_get_featured_banners
Auth Admin required
Purpose Get current featured banners config

Params: None (ignore payload)

Returns (JSON):

Field Type Description
banners array Current banners (may be empty)

5. Gacha Module (gacha.lua)

↑ Back to top

Gacha Banners

↑ Back to top

Banner ID Name Currency Pull 1 Cost Pull 10 Cost Pity At
star Star Banner star 160 1440 90
gold Gold Banner gold 50 450 90

Drop Rates

↑ Back to top

Rarity Probability Pool Items
real_prize 1.0% skin_gacha_rainbow_suit, skin_gacha_dragon_hat, skin_gacha_phantom_gloves, skin_gacha_neon_acc
rare 14.0% frag_rare
uncommon 25.0% frag_uncommon
common 60.0% frag_common

Real Prize Catalog

↑ Back to top

Item ID Name Category
skin_gacha_rainbow_suit Rainbow Suit costume
skin_gacha_dragon_hat Dragon Hat head
skin_gacha_phantom_gloves Phantom Gloves glove
skin_gacha_neon_acc Neon Accessory accessory

Fragment Items

↑ Back to top

ID Name Rarity
frag_common Common Fragment common
frag_uncommon Uncommon Fragment uncommon
frag_rare Rare Fragment rare

RPC: perform_gacha_pull

↑ Back to top

Field Value
Function gacha.rpc_perform_gacha_pull
Auth Required
Purpose Perform 1 or 10 gacha pulls on a banner

Params (JSON):

Field Type Required Default Description
banner_id string Yes -- "star" or "gold"
count number No 1 Number of pulls (1 or 10)

Returns (JSON):

Field Type Description
success bool Always true
results array Array of pull results: {id, rarity, name}
new_pity number Updated pity counter for this banner

Errors:

  • "Not authenticated"
  • "Banner ID required"
  • "Invalid count"
  • "Unknown banner: <banner_id>"
  • "Could not read account"
  • "Insufficient currency"
  • "Failed to update wallet"
  • "Failed to write storage: <err>"

Pity system: After pity_at (90) pulls without a real_prize, the next pull is guaranteed real_prize. Pity counter resets on real_prize drop. Pity counter persists per banner in storage.

Storage reads/writes:

  • Collection "profiles", key "pity_counters": per-banner pity counts
  • Collection "profiles", key "fragments": fragment inventory
  • Collection "inventory", key = item_id: real prize items

Wallet: Deducts currency cost. For count=10, uses pull_10_cost if available, otherwise pull_1_cost * count.


6. Leaderboard Module (leaderboard.lua)

↑ Back to top

Leaderboard Config

↑ Back to top

Property Value
ID "global_high_score"
Authoritative true
Sort Order "desc" (highest first)
Operator "best" (best score kept)
Reset Schedule nil (never resets)

RPC: get_leaderboard_stats

↑ Back to top

Field Value
Function leaderboard.rpc_get_leaderboard_stats
Auth None
Purpose Get top 50 leaderboard entries

Params: None (ignore payload)

Returns (JSON):

Field Type Description
leaderboard array Top 50 leaderboard entries

Leaderboard entry object:

Field Type Description
user_id string Owner user ID
username string Username
display_name string Username (fallback -- actually record.username)
avatar_url string Avatar URL from metadata
loadout_character string Character from metadata (default "Copper")
high_score number Score
games_played number From metadata
games_won number From metadata

RPC: submit_score

↑ Back to top

Field Value
Function leaderboard.rpc_submit_score
Auth Required
Purpose Submit/update own score on the leaderboard

Params (JSON):

Field Type Required Default Description
score number No 0 Score to submit
games_played number No 0 Games played count
games_won number No 0 Games won count
avatar_url string No From account Avatar URL
loadout_character string No "Copper" Character selection

Returns (JSON):

Field Type Description
success bool true

Errors:

  • "Not authenticated"
  • "Failed to submit score"

RPC: sync_leaderboard

↑ Back to top

Field Value
Function leaderboard.rpc_sync_leaderboard
Auth Required
Purpose Bulk-sync all users' stats from "stats" and "profiles" collections to the native leaderboard

Params: None (ignore payload)

Returns (JSON):

Field Type Description
success bool Always true
synced number Number of records written
objects_found number Stats objects found
debug array Error messages per user

Errors:

  • "Not authenticated"
  • "Sync failed: <err>"

Storage reads: Collection "stats" (all users, limit 100), Collection "profiles" (all users, limit 100), key "profile".

RPC: reset_stats

↑ Back to top

Field Value
Function leaderboard.rpc_reset_stats
Auth Required
Purpose Delete own leaderboard record and reset stats to zero

Params: None (ignore payload)

Returns (JSON):

Field Type Description
success bool true

Storage write: Collection "stats", key "game_stats" -- zeros out games_played, games_won, high_score, total_kills, total_deaths. permission_read=2, permission_write=1.

RPC: admin_update_stats

↑ Back to top

Field Value
Function leaderboard.rpc_admin_update_stats
Auth Admin required
Purpose Force-overwrite a user's stats and leaderboard record

Params (JSON):

Field Type Required Description
user_id string Yes Target user ID
stats object Yes Stats object: {high_score, games_played, games_won, loadout_character, ...}

Returns (JSON):

Field Type Description
success bool true

Errors:

  • "Admin privileges required"
  • "User ID and stats are required"

Storage write: Collection "stats", key "game_stats", permission_read=1, permission_write=0.

RPC: admin_delete_stats

↑ Back to top

Field Value
Function leaderboard.rpc_admin_delete_stats
Auth Admin required
Purpose Delete a user's stats storage and leaderboard record

Params (JSON):

Field Type Required Description
user_id string Yes Target user ID

Storage deletes: {collection="stats", key="stats"}, {collection="stats", key="game_stats"}, plus leaderboard record.

RPC: admin_sync_leaderboard

↑ Back to top

Field Value
Function leaderboard.rpc_admin_sync_leaderboard
Auth Admin required
Purpose Same as sync_leaderboard but requires admin

Delegates to: leaderboard.rpc_sync_leaderboard (same params, same returns).


7. Inbox/Mail Module (inbox.lua)

↑ Back to top

RPC: admin_send_mail

↑ Back to top

Field Value
Function inbox.rpc_admin_send_mail
Auth Admin required
Purpose Send a personal (targeted) or global mail

Params (JSON):

Field Type Required Default Description
title string No "Announcement" Mail title
content string No "" Mail body
start_date string No Current UTC ISO8601 start date
end_date string No "" ISO8601 end date (global only)
rewards array/object No {} Attached rewards (see claim_mail_reward for format)
target_user_id string No "" If set -> personal mail to this user. Omit -> global mail

Returns (JSON):

Field Type Description
success bool true
mail object The created mail object

Mail object fields: id (UUIDv4), title, content, sender ("TEKTON DEV TEAM"), date, start_date, end_date, expiry_date (30 days from now), rewards, type ("personal" or "global").

Personal storage: Collection "inbox", key "personal", permission_read=1, permission_write=0. Global storage: Collection "config", key "global_mail", user SYSTEM_USER_ID, permission_read=2, permission_write=0.

RPC: get_mail

↑ Back to top

Field Value
Function inbox.rpc_get_mail
Auth Required
Purpose Get all non-deleted, non-expired personal + global mail for the user

Params: None (ignore payload)

Returns (JSON):

Field Type Description
mails array Filtered mail objects (excludes deleted, expired, not-yet-started, ended global)
state object {claimed_ids, deleted_ids, read_ids}

Filtering logic: Excludes mail where:

  • ID is in state.deleted_ids
  • expiry_date is in the past
  • start_date is in the future
  • Global mail with end_date in the past

Storage reads:

  • Collection "inbox", key "personal", user = caller
  • Collection "config", key "global_mail", user = SYSTEM_USER_ID
  • Collection "inbox", key "state", user = caller

RPC: claim_mail_reward

↑ Back to top

Field Value
Function inbox.rpc_claim_mail_reward
Auth Required
Purpose Claim rewards attached to a mail

Params (JSON):

Field Type Required Description
mail_id string Yes Mail ID to claim rewards from

Returns (JSON):

Field Type Description
success bool true
claimed_ids array Updated list of claimed mail IDs

Reward format (in the mail object):

Legacy dict format: {star=100, gold=50} Array format: [{type="star", amount=100}, {type="gold", amount=50}, {type="frag_rare", id="frag_rare", amount=1}, {type="skin", id="skin_gacha_rainbow_suit"}]

Reward type Behavior
"star" Added to wallet
"gold" Added to wallet
"frag_" prefix Added to inventory fragments (key: "fragments")
"item" Added to inventory fragments
"skin" Added to inventory (key = skin item ID)

Errors:

  • "Not authenticated"
  • "mail_id required"
  • "Reward already claimed"
  • "Mail not found"

Storage writes: Wallet update, then collection "inventory" (fragments or skins), then "inbox"/"state".

RPC: delete_mail

↑ Back to top

Field Value
Function inbox.rpc_delete_mail
Auth Required
Purpose Soft-delete a mail (adds ID to state.deleted_ids)

Params (JSON):

Field Type Required Description
mail_id string Yes Mail ID to delete

RPC: save_mail_state

↑ Back to top

Field Value
Function inbox.rpc_save_mail_state
Auth Required
Purpose Mark mails as read

Params (JSON):

Field Type Required Description
read_ids array No Array of mail IDs to mark as read

RPC: admin_list_mail

↑ Back to top

Field Value
Function inbox.rpc_admin_list_mail
Auth Admin required
Purpose List all mail (global + personal) across all users

Params: None (ignore payload)

Returns (JSON):

Field Type Description
mails array All mails sorted by date descending. Personal mails have extra fields: type="personal", target_user_id

RPC: admin_update_mail

↑ Back to top

Field Value
Function inbox.rpc_admin_update_mail
Auth Admin required
Purpose Edit or move a mail between users

Params (JSON):

Field Type Required Description
mail_id string Yes Mail ID to update
type string No "global" or "personal"
target_user_id string If personal Current owner (for lookup)
new_target_user_id string No Transfer to a different user
title string No New title
content string No New content
end_date string No New end date
expiry_date string No New expiry date

RPC: admin_delete_mail_server

↑ Back to top

Field Value
Function inbox.rpc_admin_delete_mail_server
Auth Admin required
Purpose Permanently delete mail from server storage

Params (JSON):

Field Type Required Description
mail_id string Yes Mail ID to delete
type string No "global" or "personal"
target_user_id string If personal Owner user ID

8. Daily Rewards Module (daily_rewards.lua)

↑ Back to top

Default Reward Schedule

↑ Back to top

Days 1-31, each day gives star currency: min(10 + (day-1)*5, 100).

Day Reward
1 10 star
2 15 star
... ...
19 100 star (capped)
20-31 100 star each

RPC: claim_daily_reward

↑ Back to top

Field Value
Function daily_rewards.rpc_claim_daily_reward
Auth Required
Purpose Claim today's daily reward

Params: None (ignore payload)

Returns (JSON):

Field Type Description
success bool true
reward_type string "star", "gold", or a fragment ID (starts with "frag_")
reward_amount number Amount rewarded
day number Day number (1-indexed)

Errors:

  • "Not authenticated"
  • "Already claimed today" -- last_claim_date == today
  • "Already claimed all rewards for this month" -- day index >= rewards array length
  • "Already claimed today's reward" -- day index already in claimed_days

Storage reads/writes:

  • Read: Collection "daily_rewards", key "state" (per-user)
  • Read: Collection "config", key "daily_rewards", user SYSTEM_USER_ID (config)
  • Write: Collection "daily_rewards", key "state" (updates claimed days)
  • Write (if fragment): Collection "inventory", key "fragments"
  • Wallet update for star/gold rewards

Reward types: "star" or "gold" -> wallet update. "frag_" prefix -> inventory fragments.

RPC: get_daily_reward_state

↑ Back to top

Field Value
Function daily_rewards.rpc_get_daily_reward_state
Auth Required
Purpose Get current daily reward state and month config

Params: None (ignore payload)

Returns (JSON):

Field Type Description
state object {claimed_days, last_claim_date, month}
month_rewards array Reward config for current month (array of {type, amount})
can_claim_today bool Whether user can claim today
today_date string Today's UTC date (YYYY-MM-DD)
today_index number Today's 0-based index
server_month number Current month number

RPC: set_daily_reward_config [Admin]

↑ Back to top

Field Value
Function daily_rewards.rpc_set_daily_reward_config
Auth Admin required
Purpose Set monthly daily reward configuration

Params (JSON):

Field Type Required Description
config object Yes Map of month -> array of {type, amount} rewards. E.g. {"01": [{type="star", amount=50}, ...]}

RPC: get_daily_reward_config_admin [Admin]

↑ Back to top

Field Value
Function daily_rewards.rpc_get_daily_reward_config_admin
Auth Admin required
Purpose Get the current daily reward config

9. Admin Module (admin.lua)

↑ Back to top

RPC: admin_kick_player

↑ Back to top

Field Value
Function admin.rpc_admin_kick_player
Auth Admin or match host
Purpose Kick a player from a match

Params (JSON):

Field Type Required Description
match_id string Yes Match ID
user_id string Yes Player to kick
reason string No "Kicked by admin"

Errors:

  • "Admin or host privileges required"
  • "Cannot kick yourself"
  • "Failed to kick player"

Mechanism: Sends nk.match_signal with {action="kick", user_id, reason}.

RPC: admin_ban_player

↑ Back to top

Field Value
Function admin.rpc_admin_ban_player
Auth Admin required
Purpose Ban a player (sets metadata.banned, kicks from match)

Params (JSON):

Field Type Required Description
user_id string Yes User to ban
reason string No "Banned by admin"
duration_hours number No Hours until ban expires (omitted = permanent)
match_id string No Also kick from this match

Errors:

  • "Admin privileges required"
  • "Cannot ban yourself"
  • "Target account not found"
  • "Cannot ban an admin"

Storage: Writes ban record to collection "bans", key = user_id, user = SYSTEM_USER_ID, permission_read=2, permission_write=0.

RPC: admin_unban_player

↑ Back to top

Field Value
Function admin.rpc_admin_unban_player
Auth Admin required
Purpose Remove ban from a player

Params (JSON):

Field Type Required
user_id string Yes

RPC: admin_get_ban_list

↑ Back to top

Field Value
Function admin.rpc_admin_get_ban_list
Auth Admin required
Purpose List all ban records

RPC: admin_get_server_stats

↑ Back to top

Field Value
Function admin.rpc_admin_get_server_stats
Auth Admin (or host if match_id provided)
Purpose Get server stats (active matches, players)

Params (JSON):

Field Type Required Description
match_id string No If set, includes match-specific stats

Returns (JSON):

Field Type Description
active_matches number Count of active matches
total_players number Total players in matches
server_time number os.time()
match object (optional) {id, size, tick_rate, authoritative}

RPC: admin_end_match

↑ Back to top

Field Value
Function admin.rpc_admin_end_match
Auth Admin or match host
Purpose Signal a match to end

Params (JSON):

Field Type Required Description
match_id string Yes Match ID
reason string No "Ended by admin"

RPC: admin_set_user_role

↑ Back to top

Field Value
Function admin.rpc_admin_set_user_role
Auth Owner only (role must be "owner")
Purpose Set a user's role

Params (JSON):

Field Type Required Valid Values
user_id string Yes Target user
role string Yes "player", "moderator", "admin"

Errors:

  • "Only owners can modify user roles"
  • "Invalid role"

RPC: admin_topup_gold

↑ Back to top

Field Value
Function admin.rpc_admin_topup_gold
Auth Admin required
Purpose Add 999,999 gold to own wallet (dev quick-fill)

Wallet update: {gold = 999999}

RPC: admin_clear_global_chat

↑ Back to top

Field Value
Function admin.rpc_admin_clear_global_chat
Auth Admin required
Purpose Delete all messages from a chat channel

Params (JSON):

Field Type Required Description
channel_id string Yes Channel ID or room name

Returns (JSON): {success, deleted}

RPC: admin_list_users

↑ Back to top

Field Value
Function admin.rpc_admin_list_users
Auth Admin required
Purpose List all users (limit 500) with role and ban info

Returns (JSON): {users: [{user_id, username, display_name, create_time, role, banned, ban_reason}], count}

RPC: admin_delete_users

↑ Back to top

Field Value
Function admin.rpc_admin_delete_users
Auth Admin required
Purpose Permanently delete user accounts

Params (JSON):

Field Type Required Description
user_ids array Yes Array of user IDs to delete

Errors:

  • "Cannot delete your own account"
  • "Cannot delete admin account" (prevents deleting admin/moderator/owner)

Returns (JSON): {success, deleted: [user_ids], failed: [{user_id, reason}]}

RPC: admin_get_user_detail

↑ Back to top

Field Value
Function admin.rpc_admin_get_user_detail
Auth Admin required
Purpose Get comprehensive user details (profile, friends, purchases, storage, wallet ledger)

Params (JSON):

Field Type Required Default Description
user_id string Yes -- Target user ID
collections array No ["profiles","stats","inventory","receipts","history","matches","inbox"] Storage collections to include

Returns (JSON): Rich object with {user, friends, purchases, wallet_ledger, storage, subscription}.

RPC: admin_update_user_identity

↑ Back to top

Field Value
Function admin.rpc_admin_update_user_identity
Auth Admin required
Purpose Update a user's identity fields (username, display_name, timezone, location, lang_tag, avatar_url, metadata)

Params (JSON):

Field Type Required
user_id string Yes
username string No
display_name string No
timezone string No
location string No
lang_tag string No
avatar_url string No
metadata object No (merged into existing metadata)

RPC: admin_set_user_password

↑ Back to top

Field Value
Function admin.rpc_admin_set_user_password
Auth Admin required
Purpose Force-set a user's password (user must have email credential)

Params (JSON):

Field Type Required
user_id string Yes
password string Yes

Errors:

  • "User has no email credential"
  • "user_id and password are required"

RPC: admin_get_player_list

↑ Back to top

Field Value
Function admin.rpc_admin_get_player_list
Auth Admin or match host
Purpose Get player list for a match (stub -- returns empty)

RPC: admin_get_chat_config

↑ Back to top

Field Value
Function admin.rpc_admin_get_chat_config
Auth Admin required
Purpose Get lobby chat configuration

Returns (JSON): {config: {prefix, max_messages, max_age_days}}

RPC: admin_set_chat_config

↑ Back to top

Field Value
Function admin.rpc_admin_set_chat_config
Auth Admin required
Purpose Set lobby chat configuration

Params (JSON):

Field Type Required Default Description
prefix string No "" Chat prefix
max_messages number No 50 Max messages
max_age_days number No 0 Max message age

Storage: Collection "config", key "lobby_chat", user SYSTEM_USER_ID.

RPC: admin_purge_old_messages

↑ Back to top

Field Value
Function admin.rpc_admin_purge_old_messages
Auth Admin required
Purpose Delete chat messages older than specified days

Params (JSON):

Field Type Required Description
channel_id string Yes Channel ID or room name
max_age_days number Yes Delete messages older than this

RPC: admin_list_channel_messages

↑ Back to top

Field Value
Function admin.rpc_admin_list_channel_messages
Auth Admin required
Purpose List messages in a chat channel

Params (JSON):

Field Type Required Default Description
channel_id string Yes -- Channel ID or room name
limit number No 50 Messages per page
cursor string No "" Pagination cursor
forward bool No true Direction

RPC: admin_delete_channel_message

↑ Back to top

Field Value
Function admin.rpc_admin_delete_channel_message
Auth Admin required
Purpose Delete a specific chat message

Params (JSON):

Field Type Required
channel_id string Yes
message_id string Yes

10. Storage Collections Reference

↑ Back to top

Collection: "shop_config"

↑ Back to top

Key User Permission Read Permission Write Schema
featured_banners SYSTEM 2 (public) 0 (owner) {banners: [{item_id, ...}]}

Collection: "receipts"

↑ Back to top

Key User Permission Read Permission Write Schema
<idempotency_key> Owner 1 (owner-read) 0 (owner) {type, package_id/ item_id, status, changeset, receipt, processed_at, ...}

Collection: "inventory"

↑ Back to top

Key User Permission Read Permission Write Schema
<item_id> Owner 1 (owner-read) 0 (owner) {category, purchased_at, quantity} (or {acquired_via, purchased_at} for skins)
"fragments" Owner 1 (owner-read) 0 (owner) {frag_common: N, frag_uncommon: N, frag_rare: N, ...}

Collection: "profiles"

↑ Back to top

Key User Permission Read Permission Write Schema
"pity_counters" Owner 1 (owner-read) 0 (owner) {star: N, gold: N} (per-banner pity)
"fragments" Owner 1 (owner-read) 0 (owner) Fragment counts (same as inventory fragments)
"profile" Owner 2 (public) 0 (owner) Profile data (avatar_url, loadout_character, etc.)

Collection: "history"

↑ Back to top

Key User Permission Read Permission Write Schema
"logins" Owner 0 (no-read) 0 (owner) {logins: [{time, ip}, ...]} (max 20)

Collection: "stats"

↑ Back to top

Key User Permission Read Permission Write Schema
"game_stats" Owner 1 (owner-read) or 2 (public) 1 (owner-write) or 0 (owner) {games_played, games_won, high_score, total_kills, total_deaths, avatar_url, loadout_character}
"stats" Owner Varies 0 (owner) Legacy/user-level stats

Collection: "inbox"

↑ Back to top

Key User Permission Read Permission Write Schema
"personal" Owner 1 (owner-read) 0 (owner) {mails: [{id, title, content, sender, date, start_date, end_date, expiry_date, rewards, type}]}
"state" Owner 1 (owner-read) 0 (owner) {claimed_ids: [string], deleted_ids: [string], read_ids: [string]}

Collection: "daily_rewards"

↑ Back to top

Key User Permission Read Permission Write Schema
"state" Owner 1 (owner-read) 0 (owner) {claimed_days: [number], last_claim_date: "YYYY-MM-DD", month: "MM"}

Collection: "config" (system-level)

↑ Back to top

Key User Permission Read Permission Write Schema
"daily_rewards" SYSTEM 2 (public) 0 (owner) {"MM": [{type, amount}, ...]} (month->rewards map)
"global_mail" SYSTEM 2 (public) 0 (owner) {mails: [{id, title, content, ...}]}
"lobby_chat" SYSTEM 2 (public) 0 (owner) {prefix, max_messages, max_age_days}

Collection: "bans"

↑ Back to top

Key User Permission Read Permission Write Schema
<user_id> SYSTEM 2 (public) 0 (owner) {user_id, username, banned_by, banned_at, reason, expires}

Collection: "matches" (per-user)

↑ Back to top

Key User Permission Schema
<match_id> Owner 0 (owner) Match result data (keyed by match ID, varies)

11. Leaderboard Config

↑ Back to top

Property Value
ID "global_high_score"
Authoritative true (can only be written server-side)
Sort Order "desc" (highest -> lowest)
Operator "best" (keep best score per user)
Reset nil (never auto-resets)
Created By Both core.lua and leaderboard.lua on module load

12. Error Strings Reference

↑ Back to top

Auth errors (raised by utils)

↑ Back to top

Error String Trigger
"Admin privileges required" Non-admin calls admin RPC
"Admin or host privileges required" Non-admin, non-host calls admin/host RPC
"Not authenticated" context.user_id is nil

Economy errors

↑ Back to top

Error String Trigger
"Package ID required" buy_currency called without package_id
"Idempotency key required" Missing idempotency_key
"Invalid package ID" Unknown package_id
"InvalidReceipt" IAP receipt validation failed
"NotEnoughFunds" Insufficient wallet balance for purchase
"ItemNotFound" Item ID not in shop catalog
"Invalid quantity" Quantity < 1
"PurchaseFailed" Storage write failed during item purchase
"Item not found in catalog: <id>" Admin tried to banner a non-catalog item

User errors

↑ Back to top

Error String Trigger
"Account not found" User ID doesn't exist
"Account banned until <time>. Reason: <reason>" Temporary ban
"Account permanently banned. Reason: <reason>" Permanent ban
"Failed to update profile" nk.account_update_id failed
"Current password required" Email change without current password
"Incorrect current password." Email re-auth failed
"Failed to set new credentials: <err>" Link email failed
"Missing to_user_id or match_id" Lobby invite missing fields
"Cannot add yourself" Friend request to self
"user_id is required" Missing target in friend request

Gacha errors

↑ Back to top

Error String Trigger
"Banner ID required" Missing banner_id
"Invalid count" Count < 1
"Unknown banner: <id>" Invalid banner
"Could not read account" Account lookup failed
"Insufficient currency" Not enough star/gold
"Failed to update wallet" Wallet update failed
"Failed to write storage: <err>" Storage write failed

Leaderboard errors

↑ Back to top

Error String Trigger
"Failed to submit score" Leaderboard write failed
"Sync failed: <err>" Storage list failed
"User ID and stats are required" Missing params in admin_update_stats

Inbox errors

↑ Back to top

Error String Trigger
"mail_id required" Missing mail ID
"Reward already claimed" Mail already in claimed_ids
"Mail not found" Mail ID not in any mailbox
"Mail not found in global" Update/delete: mail not in global
"Mail not found in personal inbox" Update/delete: mail not in personal
"target_user_id required for personal mail" Missing owner for personal mail operations

Daily reward errors

↑ Back to top

Error String Trigger
"Already claimed today" last_claim_date == today
"Already claimed all rewards for this month" Day index >= reward array length
"Already claimed today's reward" Day index already in claimed_days

Admin errors

↑ Back to top

Error String Trigger
"Cannot kick yourself" Self-kick attempt
"Failed to kick player" Match signal failed
"Cannot ban yourself" Self-ban attempt
"Target account not found" Account lookup failed
"Cannot ban an admin" Target is admin/moderator/owner
"Only owners can modify user roles" Non-owner tries role change
"Invalid role" Role not in {player, moderator, admin}
"channel_id is required" Missing channel ID for chat operations
"channel_id and message_id are required" Missing chat message params
"Failed to delete message: <err>" Channel message remove failed
"Failed to list messages: <err>" Channel list failed
"max_age_days must be > 0" Invalid purge param
"Match not found" Match lookup failed
"User has no email credential" Cannot set password for non-email user
"Cannot delete your own account" Self-deletion attempt
"Cannot delete admin account" Deleting admin/moderator/owner
"No user IDs provided" Empty user_ids array

13. Permission Levels

↑ Back to top

Role Hierarchy

↑ Back to top

Role Can Call Admin RPCs Can Modify Roles Notes
"player" No No Default role
"moderator" Yes No Same as admin for auth checks
"admin" Yes No Standard admin
"owner" Yes Yes (only owner) Can set player, moderator, admin roles

Auth Check Functions Summary

↑ Back to top

Check What It Allows
utils.require_admin Any RPC gated by this. Roles: admin, moderator, owner
utils.require_admin_or_host Admin/moderator/owner, OR match host (state.hostUserId)
metadata.role == "owner" Only admin_set_user_role

Storage Permission Bits

↑ Back to top

Value Meaning
0 Owner only (no one can read/write except server)
1 Owner read, server write
2 Public read, server write

System User ID

↑ Back to top

00000000-0000-0000-0000-000000000000

Used for global/shared storage collections (bans, config, global_mail, shop_config).


RPC Summary Table

↑ Back to top

RPC Name Module Auth Admin?
get_user_profile user None No
update_user_profile user Required No
search_users user Required No
change_credentials user Required No
send_lobby_invite user Required No
send_friend_request user Required No
admin_get_user_history user Required Yes
get_shop_catalog economy Required No
buy_currency economy Required No
purchase_item economy Required No
admin_set_featured_banners economy Required Yes
admin_get_featured_banners economy Required Yes
perform_gacha_pull gacha Required No
get_leaderboard_stats leaderboard None No
submit_score leaderboard Required No
sync_leaderboard leaderboard Required No
reset_stats leaderboard Required No
admin_update_stats leaderboard Required Yes
admin_delete_stats leaderboard Required Yes
admin_sync_leaderboard leaderboard Required Yes
admin_send_mail inbox Required Yes
get_mail inbox Required No
claim_mail_reward inbox Required No
delete_mail inbox Required No
save_mail_state inbox Required No
admin_list_mail inbox Required Yes
admin_update_mail inbox Required Yes
admin_delete_mail_server inbox Required Yes
claim_daily_reward daily_rewards Required No
get_daily_reward_state daily_rewards Required No
set_daily_reward_config daily_rewards Required Yes
get_daily_reward_config_admin daily_rewards Required Yes
admin_kick_player admin Required Admin or host
admin_ban_player admin Required Yes
admin_unban_player admin Required Yes
admin_get_ban_list admin Required Yes
admin_get_server_stats admin Required Admin or host
admin_end_match admin Required Admin or host
admin_set_user_role admin Required Owner only
admin_topup_gold admin Required Yes
admin_clear_global_chat admin Required Yes
admin_list_users admin Required Yes
admin_delete_users admin Required Yes
admin_get_user_detail admin Required Yes
admin_update_user_identity admin Required Yes
admin_set_user_password admin Required Yes
admin_get_player_list admin Required Admin or host
admin_get_chat_config admin Required Yes
admin_set_chat_config admin Required Yes
admin_purge_old_messages admin Required Yes
admin_list_channel_messages admin Required Yes
admin_delete_channel_message admin Required Yes

Wallet Update Summary

↑ Back to top

Operation Changeset Context
IAP: gold packages (verified) {gold: +N} buy_currency
IAP: star packages {star: +N, gold: -N} buy_currency (costs gold)
Purchase item {gold: -N} or {star: -N} purchase_item
Gacha pull {<currency>: -cost} perform_gacha_pull
Claim mail rewards {star: +N} and/or {gold: +N} claim_mail_reward
Daily reward {star: +N} or {gold: +N} claim_daily_reward
Admin gold top-up {gold: 999999} admin_topup_gold

All wallet updates use nk.wallet_update(user_id, changeset, {}, true) (third arg true = update only, no metadata).