Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
HORIZON_API_KEYYesYour horizOn API key. Required for live API tools. Get one at horizon.pm
HORIZON_BASE_URLNoAPI base URL. Defaults to https://horizon.pmhttps://horizon.pm
HORIZON_ACCOUNT_API_KEYNoYour horizOn Account Key for admin tools. Optional. Admin tools only register when this is set.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": true
}
prompts
{
  "listChanged": true
}
resources
{
  "listChanged": true
}

Tools

Functions exposed to the LLM to take actions

NameDescription
horizon_test_connectionA

Checks that HORIZON_API_KEY and HORIZON_BASE_URL reach the horizOn API by fetching the app's remote configs; takes no input. Use when: first during setup, and whenever other tools fail, to tell a configuration problem from a problem of the call itself. Not for: checking a player's session (use horizon_check_auth) or reading config values for the game (use horizon_get_all_remote_configs). Side effects: None (read only). Returns: {success: true, data} where data holds the app's remote configs; success means key and URL work. Errors: A network error means HORIZON_BASE_URL is wrong or unreachable: check the URL (default https://horizon.pm). Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_signup_anonymousA

Creates a new anonymous (guest) player account that needs only a display name and returns its anonymousToken. Use when: a player starts without email, for example on first launch of a game with guest play. Not for: players who sign up with email and password (use horizon_signup_email) or returning guests (use horizon_signin_anonymous with the stored anonymousToken). Side effects: creates one new player account per call; calling it again creates another account, it never finds an existing one. Sign-up starts no session. Returns: {userId, username, isAnonymous: true, anonymousToken, createdAt}. Store anonymousToken on the device: it is the only way back into this account. Then call horizon_signin_anonymous to get the accessToken that session tools need. Errors: 400 for an invalid displayName: fix the name and retry. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_signup_emailA

Creates a new player account with email, password and display name; the account starts unverified and without a session. Use when: a new player registers with email so the account can be recovered on other devices. Not for: existing accounts (use horizon_signin_email) and guest play without email (use horizon_signup_anonymous). Side effects: creates one player account and sends the verification email. It does not sign in. Returns: the new user with userId and isVerified: false. After the player verified the email address, call horizon_signin_email to get the accessToken. Errors: 409 when the email already belongs to an account: sign in with horizon_signin_email instead. 400 for an invalid email, a password outside 8 to 128 characters or a common password, or a bad name: fix the value and retry. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_signin_emailA

Signs in an existing email account with email and password and starts a player session (accessToken). Use when: a registered, verified player logs in, before any tool that needs sessionToken (submit score, cloud save, gift code redeem, profile, validated runs). Not for: guest accounts (use horizon_signin_anonymous), new players (use horizon_signup_email) or checking an existing token (use horizon_check_auth). Side effects: creates a new session on every call; older sessions stay valid. Returns: {userId, username, email, accessToken, authStatus, message}. Pass userId and accessToken (as sessionToken) to the session tools; the token stays valid for one hour after its last use. Errors: 401 for a wrong password or unknown email: ask the player to check the credentials. 403 (NOT_VERIFIED) for an unverified email: the player must confirm the verification email first. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_signin_anonymousA

Signs in an anonymous (guest) account with its stored anonymousToken and starts a player session (accessToken). Use when: a returning guest opens the game, or right after horizon_signup_anonymous, before any tool that needs sessionToken. Not for: email accounts (use horizon_signin_email) or creating a guest (use horizon_signup_anonymous). Requires: the anonymousToken returned by horizon_signup_anonymous for this account. Side effects: creates a new session on every call; older sessions stay valid. Returns: {userId, username, accessToken, authStatus, message}. Pass userId and accessToken (as sessionToken) to the session tools; the token stays valid for one hour after its last use. Errors: 404 for an unknown anonymousToken: the account is lost, create a new one with horizon_signup_anonymous. 429 also comes from the per-token limit (5 attempts per minute) and the per-IP limit (30 per minute): wait a minute. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_check_authA

Checks whether a player's session token (accessToken) is still valid for the given user. Use when: before a session tool when the token may have expired, or when a game resumes, to decide whether the player must sign in again. Not for: testing the API key or base URL (use horizon_test_connection) or getting a new token (use horizon_signin_email or horizon_signin_anonymous). Requires: userId and the accessToken from horizon_signin_email or horizon_signin_anonymous. Side effects: a valid check refreshes the session's one hour idle timeout; nothing else changes. Returns: {userId, isAuthenticated, authStatus, message}. An invalid or expired token is not an HTTP error: isAuthenticated is false, so sign in again with horizon_signin_*. Errors: 400 for a malformed userId or token: pass the values from the sign-in result. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_list_leaderboardsA

Lists the leaderboard boards of this API key with their keys, sort order and whether they accept only validated runs. Use when: first, when a game has several boards (weekly, per level) or before submitting, to learn the leaderboardKey values and whether a board is validatedOnly. Not for: reading entries (use horizon_get_leaderboard_top, horizon_get_user_rank or horizon_get_leaderboard_around). Side effects: None (read only). Returns: {boards: [{key, name, sortOrder, isActive, scoreCount, validatedOnly}], totalElements}. Pass a key as leaderboardKey to the submit, top, rank and around tools; without it they use the default board. validatedOnly true means scores are accepted only through horizon_start_run plus horizon_submit_validated. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_submit_scoreA

Submits a player's score to a leaderboard; the board keeps only each player's best score (higher on DESC boards, lower on ASC boards). Use when: a round ends on a normal board. Then call horizon_get_user_rank to show the new position. Not for: boards marked validatedOnly in horizon_list_leaderboards (use horizon_start_run and horizon_submit_validated) and reading scores (use horizon_get_leaderboard_top). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Side effects: replaces the player's entry only when the new score beats it; a worse or equal score, or the same call again, changes nothing. Returns: {success: true} (also when the score did not beat the best one). Errors: 403 VALIDATED_SUBMIT_REQUIRED on a validatedOnly board, nothing written: switch to horizon_start_run and horizon_submit_validated, do not retry. 403 PLAYER_BANNED when a moderator banned the player from the board: final, do not retry. 404 for an unknown leaderboardKey: list valid keys with horizon_list_leaderboards. 401 means the session is missing or expired: sign in again and retry with the new accessToken. 403 for a session means the token belongs to another user: pass the userId that signed in. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_leaderboard_topA

Returns the top entries of a leaderboard from position 1, each with the player's profile, plus the requesting player's own entry when they are outside the list. Use when: a game shows a global top list (top 10, top 100). Not for: one player's position alone (use horizon_get_user_rank) or the players around them (use horizon_get_leaderboard_around). Requires: a userId from horizon_signup_* or horizon_signin_* (used to append the player's own entry); no session token. Side effects: None (read only). Returns: {entries: [{position, username, score, profile}]} in board order. When the userId is not among them, the player's own entry with the real position is added at the end. An empty board returns an empty list. profile is {avatarId, frameId, badges} (IDs from the cosmetics catalog; null or [] when not set, set with horizon_set_profile). Errors: 404 for an unknown leaderboardKey: list valid keys with horizon_list_leaderboards. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_user_rankA

Returns one player's own position, score and profile on a leaderboard. Use when: after horizon_submit_score, or to show 'your rank' in the game. Not for: a list of entries (use horizon_get_leaderboard_top or horizon_get_leaderboard_around). Requires: a userId from horizon_signup_* or horizon_signin_*; no session token. Side effects: None (read only). Returns: {position, username, score, profile}. profile is {avatarId, frameId, badges} (IDs from the cosmetics catalog; null or [] when not set, set with horizon_set_profile). Errors: 404 when the player has no score on this board yet (submit one first) or the leaderboardKey is unknown (check horizon_list_leaderboards). Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_leaderboard_aroundA

Returns the leaderboard entries directly above and below a player's own position, including the player, each with profile. Use when: a game shows 'you and your rivals' instead of the global top. Not for: the global top list (use horizon_get_leaderboard_top) or the position alone (use horizon_get_user_rank). Requires: a userId from horizon_signup_* or horizon_signin_*; no session token. Side effects: None (read only). Returns: {entries: [{position, username, score, profile}]} in board order. profile is {avatarId, frameId, badges} (IDs from the cosmetics catalog; null or [] when not set, set with horizon_set_profile). Errors: 404 for an unknown leaderboardKey: list valid keys with horizon_list_leaderboards. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_save_cloud_dataA

Stores a player's save game as one string (usually JSON) on the server, replacing the player's previous save. Use when: the game saves progress so it survives reinstalls and can be loaded on another device. Not for: reading the save (use horizon_load_cloud_data) or server-owned currency that players must not edit (use Validated Actions: horizon_submit_validated and horizon_get_state). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Side effects: overwrites the player's single save slot; the old save is gone. Saving the same data again changes nothing. Returns: {success: true, dataSizeBytes}. Errors: 403 when the data exceeds the tier limit (defaults FREE 1 KB, BASIC 5 KB, PRO 20 KB, ENTERPRISE 250 KB): shrink the save or upgrade. 401 means the session is missing or expired: sign in again and retry with the new accessToken. 403 for a session means the token belongs to another user: pass the userId that signed in. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_load_cloud_dataA

Reads a player's save game that was stored with horizon_save_cloud_data. Use when: the game starts or the player switches devices and needs the saved progress. Not for: writing (use horizon_save_cloud_data) or balances of server-owned values (use horizon_get_state). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Side effects: None (read only). Returns: {found, saveData}. found false with saveData null means the player never saved: this is normal on first start, so initialize default data. Errors: 401 means the session is missing or expired: sign in again and retry with the new accessToken. 403 for a session means the token belongs to another user: pass the userId that signed in. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_remote_configA

Reads one remote config value by key (a feature flag or balance value set in the horizOn dashboard) as a string. Use when: the game needs a single value, for example to check one feature flag. Not for: loading many values at start (use horizon_get_all_remote_configs, one request instead of many). Side effects: None (read only). Returns: {configKey, configValue, found}. Values are always strings: parse numbers and booleans yourself. An unknown key is not an error: found is false and configValue null, so fall back to a default. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_all_remote_configsA

Reads every remote config value of the app in one call as a key to string map. Use when: at game start, to cache all flags and balance values at once. Not for: a single value (use horizon_get_remote_config) or checking the API key only (use horizon_test_connection). Side effects: None (read only). Returns: {configs, total}, where configs maps each key to its string value; an app without configs returns an empty map and total 0. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_localizationA

Reads one translated string by key in one language (default: the app's default language). Use when: the game needs a single text, for example a dynamic message. Not for: loading the UI texts at start (use horizon_get_all_localizations) or finding out which languages exist (use horizon_get_localization_languages). Side effects: None (read only). Returns: {localizationKey, value, language, found}. A missing key or translation is not an error: found is false and value null, so show a fallback text. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_all_localizationsA

Reads all translated strings of one language as a key to text map (default: the app's default language). Use when: at game start or on a language switch, to load every UI text at once. Not for: a single key (use horizon_get_localization). Side effects: None (read only). Returns: {translations, language, total}; translations is empty (total 0) when the language has no strings. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_localization_languagesA

Lists the languages that have at least one translation for the app. Use when: before horizon_get_localization or horizon_get_all_localizations, to pick a valid lang or build a language menu. Not for: the texts themselves (use horizon_get_all_localizations). Side effects: None (read only). Returns: {languages, total}, where languages holds ISO 639-1 codes such as 'en' or 'de'; empty when the app has no translations. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_newsA

Lists the published in-game news of the app (announcements, patch notes) created in the horizOn dashboard, newest first. Use when: a game shows a news feed or a message of the day, optionally filtered to the player's language. Not for: configuration values (use horizon_get_remote_config) or UI translations (use horizon_get_localization). Side effects: None (read only). Returns: an array of {id, title, message, releaseDate, languageCode} with only published entries whose release date has passed; an empty array means there is no news. Without languageCode, entries of all languages are returned. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_validate_gift_codeA

Checks whether a gift code can be redeemed by this player right now, without using it up. Use when: before horizon_redeem_gift_code, for example to confirm a code the player typed and show an error early. Not for: claiming the reward (use horizon_redeem_gift_code; validating alone grants nothing). Requires: a userId from horizon_signup_* or horizon_signin_*; no session token. Side effects: None (read only); no redemption is used. Returns: {valid}. valid false means the code does not exist, has expired, was revoked or reached its limit for this player or in total; tell the player the code does not work. Errors: Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_redeem_gift_codeA

Redeems a gift code for a player: uses up one redemption and returns the reward data and any cosmetic unlocks. Use when: the player confirms a code in the game. Check it first with horizon_validate_gift_code when unsure. Not for: only checking a code (use horizon_validate_gift_code, which uses nothing up). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Side effects: each successful call uses up one redemption and cannot be undone; cosmetic grants are added to the player's unlocks. A repeat redeems again or fails when the per-player limit is reached. Returns: {success, message, giftData, grantedUnlocks}. giftData is a JSON string with the reward set in the dashboard, which the game must parse and apply. grantedUnlocks lists the cosmetic IDs from giftData.grants the player owns after this redemption ([] without grants); show them with horizon_get_profile. Errors: 404 for an unknown code. 400 for an expired or revoked code or a reached limit: tell the player the code does not work. 409 UNLOCK_LIMIT_REACHED when the player would own more than 25 unlocks; the code is not used up. 401 means the session is missing or expired: sign in again and retry with the new accessToken. 403 for a session means the token belongs to another user: pass the userId that signed in. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_profileA

Returns a player's profile (avatar, frame, badges), the player's unlocks and the cosmetics catalog of the API key with an available flag per entry. Use when: before horizon_set_profile, to show the selection screen and learn which IDs the player may pick. Not for: changing the profile (use horizon_set_profile) or showing other players' profiles (leaderboard entries already carry them). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Side effects: None (read only). Returns: {userId, profile: {avatarId, frameId, badges}, unlocks, cosmetics: [{id, type, locked, available}], limits: {maxBadges, maxUnlocks}}. available true means this player may select the entry. Errors: 401 SESSION_REQUIRED for a missing or expired session: sign in again. 403 SESSION_FORBIDDEN for a session of another user: pass the userId that signed in. 404 PLAYER_NOT_FOUND for an unknown userId. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_set_profileA

Sets a player's avatar, frame and displayed badges. This REPLACES THE WHOLE PROFILE: an omitted, null or empty slot is cleared. Use when: the player confirms a new look. Pass the current values from horizon_get_profile for the slots that should stay. Not for: reading the profile or the catalog (use horizon_get_profile). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Every ID must be in the API key's catalog, have the type of its slot and be free or unlocked for the player; at most 3 distinct badges. Side effects: overwrites all three slots; the same call again changes nothing more. The profile shows up in leaderboard entries. Returns: the same body as horizon_get_profile after the change. Errors: The error result names the stable code. 400 INVALID_BADGES, INVALID_COSMETIC_ID, COSMETIC_NOT_FOUND or COSMETIC_TYPE_MISMATCH: pick IDs of the right type from horizon_get_profile. 403 COSMETIC_LOCKED: the cosmetic is locked and the player has not unlocked it (unlocks come from gift code grants). 401 SESSION_REQUIRED for a missing or expired session: sign in again. 403 SESSION_FORBIDDEN for a session of another user: pass the userId that signed in. 404 PLAYER_NOT_FOUND for an unknown userId. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_start_runA

Starts a server-checked run (Validated Actions): issues a single-use ticket with a server seed and starts the server's run timer. Use when: before a round whose score or earned currency must be checked by the server, and always for boards marked validatedOnly in horizon_list_leaderboards. Not for: normal boards where the client may write its own score (use horizon_submit_score) and reading balances (use horizon_get_state). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Pass leaderboardKey to bind the ticket to a board (it must exist; 'default' is created on first use). Optionally declare the start context (gameVersion, contentVersion, simulationVersion, replayFormatVersion, contentDigest, initialStateBase64); the server binds it with its own start values (rule version, cloud save, balances, seed) into the run, and a sus run keeps it in its review package. Side effects: issues a new ticket per call and counts against the run limits per player and per account; unused tickets simply expire. Returns: {runId, ticket, seed, leaderboardKey, issuedAt, expiresAt, expiresInSeconds}. Seed the game's deterministic randomness with seed, record the input log, then send the result with horizon_submit_validated and the unchanged ticket before expiresAt. Errors: Errors carry a stable code in the body and the error result names it: 400 for a bad context field, 400 INITIAL_STATE_INVALID_ENCODING (initialState not standard base64), 413 INITIAL_STATE_TOO_LARGE (decoded initialState above the plan's evidenceMaxBytes), 401 SESSION_REQUIRED, 403 SESSION_FORBIDDEN, 404 PLAYER_NOT_FOUND or LEADERBOARD_NOT_FOUND, 429 RUN_RATE_LIMITED (per player and hour) or RUN_CAPACITY_REACHED (account per UTC hour; do not retry automatically, the wait can be an hour), 503 VALIDATED_ACTIONS_UNAVAILABLE (server has no ticket key). After 401 sign in again; after 403 SESSION_FORBIDDEN pass the userId that signed in. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_submit_validatedA

Submits the result of a run started with horizon_start_run; the server checks ticket and server-only rules before it writes the score and earned values. Use when: a validated run ends: send the ticket, the claimed score (and stage) and the SHA-256 commitment of the input log. Not for: runs without a ticket (use horizon_submit_score on normal boards) and reading balances (use horizon_get_state). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. The ticket from horizon_start_run and exactly one of inputLogHash (a ready SHA-256), inputLogBase64 (raw log bytes, hashed locally) or inputLog (UTF-8 text, hashed locally); the log itself is never sent. score is required when a board is targeted (the ticket's board, or leaderboardKey) and ignored otherwise. Send earned ({key, amount}[]) only when the API key's rules define values: positive amounts earn, negative amounts spend. Side effects: consumes the ticket (single use, also when rejected); an accepted run writes the score (best score kept) and changes the balances of earned values. The same ticket cannot be submitted twice. Returns: {accepted, runId, leaderboardKey, score, bestScore, isNewHighScore, rank, durationSeconds, state, evidence, sus} plus inputLogHash (the hash that was sent). sus is true when the run was accepted but crossed a soft threshold of the rules (the flag names stay on the server); the run counts normally and the server keeps a review package. Rejected runs are never sus; an older server without the field reads as false. state is the player's server-owned values after the run: {day, values: [{key, balance, earnedToday, dailyCap, requested, credited}]}, every value key of the rules sorted by key; requested (amount sent) and credited (amount applied) appear only for the keys in earned. credited < requested on a positive amount means dailyCap or maxBalance clamped it (not an error). For a spend, credited is either requested or 0 (a concurrent run of the same player used the balance first): grant a purchase only when credited == requested. state is null when the rules define no values or the state write failed. evidence is null, or {required: true, runId, uploadBefore, maxBytes} when the server asks for the input log (the run became the player's row and is flagged or lands in the board's evidence top N, or the run is sus): upload exactly the logged bytes with horizon_upload_evidence before uploadBefore (24 hours); a log that was only hashed via inputLogHash must be kept by the caller. Errors: Rejections carry a stable code (never the rule values) and the error result names it: 400 SCORE_REQUIRED (board targeted without score) or PLAYER_NAME_REQUIRED; 401 SESSION_REQUIRED; 403 SESSION_FORBIDDEN, PLAYER_BANNED (banned from the board by a moderator, final; the ticket is not used) or SCORE_LIMIT_REACHED (ticket used up); 404 PLAYER_NOT_FOUND or LEADERBOARD_NOT_FOUND; 422 TICKET_INVALID, TICKET_EXPIRED, TICKET_FOREIGN, TICKET_CONSUMED, LEADERBOARD_MISMATCH; 422 rule codes STAGE_REQUIRED, STAGE_UNKNOWN, SCORE_ABOVE_MAX, SCORE_BELOW_MIN, STAGE_SCORE_ABOVE_MAX, STAGE_SCORE_BELOW_MIN, DURATION_TOO_SHORT, SCORE_RATE_TOO_HIGH and value codes UNKNOWN_VALUE_KEY (key not defined in the rules' values, also when the rules define none), DUPLICATE_VALUE_KEY, EARNED_ABOVE_MAX (above maxPerRun), EARNED_BELOW_MIN (below minPerRun), INSUFFICIENT_BALANCE (spend larger than the balance) (the ticket is consumed, start a new run); 503 VALIDATED_ACTIONS_UNAVAILABLE. After a 422 rule or value code or SCORE_LIMIT_REACHED the ticket is spent; after 401, 404, 429 or 503 the same ticket may be sent again. Local errors (no request sent, ticket kept): INVALID_INPUT_LOG_HASH when the hash input was missing, doubled or malformed, DUPLICATE_VALUE_KEY when earned lists a key twice. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_stateA

Returns the player's server-owned values (Validated Actions): balance, today's earnings and daily cap per value key, such as currency or loot counters. Use when: the game shows the wallet or checks whether a spend is possible before starting a run. Not for: changing balances: nothing writes a balance directly, only accepted runs through horizon_submit_validated with earned. Also not for the save game (use horizon_load_cloud_data). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. Side effects: None (read only). Returns: {userId, day, values: [{key, balance, earnedToday, dailyCap}]}: every value key defined in the API key's rules, sorted by key (balance 0 when never earned); values is empty when the rules define no values. day is the current UTC day, earnedToday the positive credit on that day (0 after midnight UTC), dailyCap null without a daily cap; the other value rules stay hidden. Games may mirror the values into cloud save for offline display but must never send them back. Errors: The error result names the stable code: 401 SESSION_REQUIRED (sign in again), 403 SESSION_FORBIDDEN (pass the userId that signed in), 404 PLAYER_NOT_FOUND, 429 without code (account request limit, wait). Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_upload_evidenceA

Uploads the input log of a validated run after horizon_submit_validated answered with evidence.required = true (evidence review). Use when: only when a submit result carries evidence.required: true, before evidence.uploadBefore (24 hours). Not for: runs without an evidence request (the server answers 404 EVIDENCE_NOT_REQUESTED) and sending the log with the submit (the submit takes only its hash). Requires: userId and the player's session: sign in with horizon_signin_email or horizon_signin_anonymous and pass its accessToken as sessionToken. The runId from the submit result and the raw input log as exactly one of inputLogBase64 (raw bytes as base64) or inputLog (UTF-8 text). Its SHA-256 must equal the inputLogHash sent with the run, so pass the same bytes or text that were hashed at submit; at most evidence.maxBytes (32,768 bytes by default). Side effects: stores the log for the developer's review; one upload per run (a second one gets 409). Returns: {runId, status: "UPLOADED", bytes} plus inputLogHash (the SHA-256 of the uploaded bytes, for comparison with the submit). Errors: Errors carry a stable code in the body and the error result names it: 400 EVIDENCE_INVALID_ENCODING (not standard base64), 401 SESSION_REQUIRED, 403 SESSION_FORBIDDEN, 404 EVIDENCE_NOT_REQUESTED (no request for this run and player; also for a run of another player), 409 EVIDENCE_ALREADY_UPLOADED, 410 EVIDENCE_EXPIRED (window passed, slot freed), 413 EVIDENCE_TOO_LARGE (decoded log above maxBytes), 422 EVIDENCE_HASH_MISMATCH (SHA-256 of the log differs from the run's inputLogHash; the request stays open, retry with the correct bytes until uploadBefore), 429 without code (account request limit). Only 422 and network errors are worth a retry; the others are final. Local error (no request sent): INVALID_INPUT_LOG when the log input was missing, doubled, empty or not base64. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_submit_feedbackA

Sends a message written by a player (bug report, feature request or comment) to the developer's feedback inbox in the horizOn dashboard. Use when: the player fills in a feedback or bug report form in the game. Not for: crashes and caught exceptions (use horizon_create_crash_report) or technical events the game logs by itself (use horizon_create_log). Requires: a userId from horizon_signup_* or horizon_signin_*; no session token. Side effects: creates one feedback entry per call; a repeat creates a duplicate. Returns: the text "ok" when the entry was stored. Errors: An unknown userId, or one of another API key, is rejected: pass the ID from the sign-in result. 403 when the account's feedback limit is reached: delete old feedback in the dashboard. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_create_logA

Writes one server-side log entry (INFO, WARN or ERROR) for a player, visible in the horizOn dashboard's user logs. Use when: the game records a technical event or a handled error for later debugging, for example a failed purchase or a slow load. Not for: crashes and exceptions with a stack trace (use horizon_create_crash_report) or messages written by players (use horizon_submit_feedback). Requires: a userId from horizon_signup_* or horizon_signin_* and the BASIC tier or higher; no session token. Side effects: creates one log entry per call; a repeat creates a duplicate. Returns: {id, createdAt} of the new entry. Errors: 403 on the FREE tier: logs need BASIC or higher, so upgrade or skip logging. 400 for an invalid userId: pass the ID from the sign-in result. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_create_crash_reportA

Submits one crash (CRASH), caught exception (NON_FATAL) or freeze (ANR) with device data to horizOn Crash Reporting, grouped by fingerprint. Use when: the game crashed (send on next start) or caught an exception worth tracking. Not for: technical events without a crash (use horizon_create_log) and messages written by players (use horizon_submit_feedback). Requires: a sessionId registered with horizon_create_crash_session for this app run; userId is optional. Side effects: adds one report per call (a repeat adds a duplicate) and marks the session as crashed. Reports with the same fingerprint form one group; a group marked resolved becomes REGRESSED when a newer app version crashes again. Returns: {id, groupId, createdAt}; groupId identifies the crash group in the dashboard. Errors: 400 when a required field is missing or too long: fix it and resend. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_create_crash_sessionA

Registers one app run (session) so horizOn can calculate the crash-free session rate. Use when: once at every app start, with a new unique sessionId; keep that ID for horizon_create_crash_report. Not for: reporting the crash itself (use horizon_create_crash_report) or player sign-in sessions (those come from horizon_signin_*). Side effects: records one session per call; a later crash report with the same sessionId marks it as crashed. Returns: {status: 'ok'}. Errors: 400 when a field is missing or too long: fix it and resend. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_send_emailA

Queues a transactional email from a dashboard template to one registered player, sent now or at a scheduled time through the developer's own SMTP server. Use when: the game triggers a message to a player, such as a welcome mail or a reminder. Not for: checking delivery (use horizon_get_email_status) or stopping a queued email (use horizon_cancel_email). Requires: an SMTP configuration and the template in the horizOn dashboard, and a recipient with a verified email address. Side effects: queues one new email per call; repeating the call sends the email again, so never repeat it to retry a status check. Returns: {id, status: 'pending', scheduledAt}; pass id to horizon_get_email_status to follow delivery or to horizon_cancel_email to stop it. Errors: 400 for an unknown templateSlug or a recipient without a verified email address: check the slug in the dashboard or pick another user. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_cancel_emailA

Cancels a queued email that is still pending (sent now or scheduled) so it is never delivered. Use when: a scheduled email is no longer wanted, for example the player already came back. Not for: emails that are already processing or sent (they cannot be recalled); check with horizon_get_email_status first when unsure. Requires: the emailId from horizon_send_email, sent with the same API key. Side effects: the email is never sent and cannot be restored; to send it later, call horizon_send_email again. Returns: {message: 'Email cancelled'}. Errors: An email that is not pending (already processing, sent, failed or cancelled) or belongs to another API key is rejected: read its state with horizon_get_email_status. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

horizon_get_email_statusA

Returns the delivery status (pending, processing, sent or failed) and details of one email queued with horizon_send_email. Use when: checking whether a sent or scheduled email was delivered, or why it failed. Not for: sending again (use horizon_send_email only for a new email) or stopping delivery (use horizon_cancel_email). Requires: the emailId from horizon_send_email. Side effects: None (read only). Returns: {id, status, templateSlug, userId, language, scheduledAt, processedAt, errorReason, createdAt}; errorReason explains a failed email (for example SMTP rejected it). Errors: An unknown emailId or one of another API key is rejected: pass the id returned by horizon_send_email. Needs HORIZON_API_KEY. Every failure returns an error result (isError) with the HTTP status and body: a 401 that horizon_test_connection also returns means HORIZON_API_KEY is wrong, 429 means the account's per-minute rate limit was reached (wait a minute, then retry).

Prompts

Interactive templates invoked by user choice

NameDescription
integrate-featureGenerate integration code for a specific horizOn feature in your game engine.
setup-authStep-by-step guide to set up horizOn authentication in your project.
debug-connectionDiagnose and fix horizOn connection issues.
explain-featureGet a detailed explanation of any horizOn feature.

Resources

Contextual data attached and managed by the client

NameDescription
overviewWhat is horizOn, core concepts (Account vs User), features, tier system, API structure, and SDKs.
docs-authAuthentication methods (Anonymous, Email, Google), endpoints, SDK code examples, and common errors.
docs-leaderboardLeaderboard score submission, top entries, user rank, entries around user, player profile per entry, validated only boards, with SDK examples.
docs-cloud-saveCloud save/load for JSON and binary data, tier size limits, SDK examples.
docs-remote-configServer-side key-value configuration: feature flags, game balance, A/B testing. SDK examples.
docs-localizationServer-side localized strings across 15 languages: per-key translations, single/all fetch, available languages. SDK examples.
docs-newsIn-game news and announcements with language filtering. SDK examples.
docs-gift-codesGift code validation and redemption for promotional rewards, including cosmetic unlocks via grants. SDK examples.
docs-player-profilePlayer avatar, frame and badges from a per-project cosmetics catalog, unlocks via gift code grants, profile in leaderboard entries. SDK examples.
docs-validated-actionsServer-checked runs: single-use run tickets with a server seed, SHA-256 input log hash, server-only rules, validated only leaderboards, server-owned player state (currency, daily caps), run start context, evidence upload (input log), admin evidence review with sus packages and package export, rejection codes. SDK examples.
docs-feedbackBug reports, feature requests, and general feedback submission. SDK examples.
docs-user-logsServer-side event and error tracking. Requires BASIC tier or higher. SDK examples.
docs-crash-reportingCrash report submission, session tracking, fingerprinting, breadcrumbs, and auto-regression detection. SDK examples.
api-referenceComplete API reference for all horizOn App API endpoints with request/response schemas.
quickstart-godotStep-by-step guide to integrate horizOn in Godot with GDScript examples.
quickstart-unityStep-by-step guide to integrate horizOn in Unity with C# examples.
docs-email-sendingTransactional and event-based email delivery to registered users. Templates, scheduling, status tracking, and SMTP integration. SDK examples.
quickstart-unrealStep-by-step guide to integrate horizOn in Unreal Engine 5.5+ with the official horizOn SDK plugin. C++ and Blueprint examples.

TDQS

A4.4/5.0

Scored across 34 tools

Disambiguation5/5

Every tool targets a distinct resource plus action across auth, leaderboards, cloud saves, remote config, localization, news, gift codes, profile, validated runs/evidence, feedback, logs, crash reporting and email. The detailed 'Use when / Not for' notes explicitly separate the few near-neighbors (submit_score vs submit_validated, get_remote_config vs get_all_remote_configs, get_leaderboard_top vs get_leaderboard_around vs get_user_rank, validate vs redeem gift code).

Naming Consistency5/5

All 34 tools share the horizon_ prefix and a consistent snake_case verb_noun pattern (get_, list_, create_, submit_, set_, cancel_, etc.). The only minor quirk is compound verbs like signup_anonymous/signin_email, but they are still uniform across the set.

Tool Count3/5

34 tools is heavy by count, yet the server spans roughly 14 distinct feature areas (auth, leaderboards, cloud save, remote config, localization, news, gift codes, profile, validated runs, evidence, feedback, logging, crash reporting, email), so most tools earn their place. Some consolidation is possible (the one/all config and localization pairs), so it sits at the borderline rather than clearly well-scoped.

Completeness4/5

The surface covers the full player-facing lifecycle for each feature area: signup/signin/check, leaderboard list/submit/top/rank/around, cloud save/load, config and localization reads, gift validate/redeem, profile get/set, validated run start/submit/evidence, feedback, logging, crash session/report, and email send/cancel/status. Minor gaps remain (no read/list for feedback, logs, crash groups or queued emails, no password reset or email verification, no session revoke or cloud-save delete), but these are dashboard-side or workable.

Maintenance

ActivityMaintained
ResponsivenessNo issues