Skip to main content
Glama

Submit Validated Run

horizon_submit_validated
Destructive

Submits a validated run result with its ticket and log hash, checking ticket and rules before writing the score and earned values.

Instructions

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).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
scoreNoClaimed score (0 to 9007199254740991). Required when a board is targeted, ignored for a run without board.
stageNoStage key for stage rules (for example "level-3")
earnedNoEarned or spent server-owned values ({key, amount}, at most 64, each key once). Every key must be defined in the values of the API key's rules (else UNKNOWN_VALUE_KEY). Omit when the game uses no server-owned values.
ticketYesticket from horizon_start_run, sent unchanged (opaque, about 190 characters, max 512)
userIdYesPlayer user ID (UUID) from horizon_signup_* or horizon_signin_*
inputLogNoInput log as UTF-8 text; the tool hashes its UTF-8 bytes locally. Use this or inputLogHash or inputLogBase64.
inputLogHashNoSHA-256 of the raw input log bytes, 64 hex characters. Use this or inputLogBase64 or inputLog.
sessionTokenYesaccessToken from horizon_signin_email or horizon_signin_anonymous for this userId; sent as a Bearer session
inputLogBase64NoRaw input log bytes as base64; the tool computes the SHA-256 locally. Use this or inputLogHash or inputLog.
leaderboardKeyNoTarget board. Omit to use the board the ticket is bound to; required for a leaderboard write with an unbound ticket.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Addedv1.6.0

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Far exceeds the annotations (destructiveHint/readOnlyHint/idempotentHint/openWorldHint): it discloses single-use ticket consumption even on rejection, which error classes spend vs. preserve the ticket, the soft-threshold 'sus' flag and review package, dailyCap/maxBalance clamping semantics (credited < requested), the concurrent-spend race where credited==0, and the 24h evidence-upload obligation. This is exactly the behavioral context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Very long, but structured with labeled sections (Use when / Not for / Requires / Side effects / Returns / Errors) and front-loaded with the purpose before the routing rules. Given 10 parameters, a stateful ticket lifecycle and dozens of error codes, nearly every sentence earns its place, though some error-code enumeration could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the full return-value burden and does so thoroughly: the response object fields, the meaning of sus, the null state/evidence cases, credited-vs-requested clamping, and the retryability of each HTTP status. Nothing an agent needs to call and interpret this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3, but the description adds genuine meaning beyond the schema: the log itself is never sent and a hash-only log must be retained by the caller, exactly one of the three log inputs is required, and earned positive/negative amounts map to earn/spend with clamping implications. Score required only when a board is targeted is reinforced with the ignore case.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Submits the result of a run started with horizon_start_run') and the server-side validation it triggers. It explicitly distinguishes itself from horizon_submit_score (runs without a ticket) and horizon_get_state (reading balances), so an agent can route without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Has explicit 'Use when', 'Not for' and 'Requires' sections that name the alternatives (horizon_submit_score, horizon_get_state, horizon_signin_*, horizon_upload_evidence) and the exact conditions selecting them. The lifecycle is unambiguous: a validated run ends, send ticket + score + log commitment.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.