Reading a response

The shape of what comes back, and how to read the footnotes on a number.

Every tool response carries a meta envelope. It is small, it is always there, and it exists because "is your data right?" is the question people actually have about a service like this.

{
  "as_of": "2026-09-08T12:34:56.000Z",
  "recorded_since": "2026-03-07T00:00:00.000Z",
  "recording_active_since": "2026-09-01T00:00:00.000Z",
  "freshness_seconds": 61,
  "source_polls": {
    "player_battlelog": {
      "observed_at": "2026-09-08T12:33:55.000Z",
      "freshness_seconds": 61
    }
  },
  "timezone_applied": "America/Chicago",
  "timeline_pending": 2,
  "request_id": "4c641bc4-0000-4000-8000-000000000001",
  "disclaimer": "This material is unofficial and is not endorsed by Supercell. For more information see Supercell’s Fan Content Policy: www.supercell.com/fan-content-policy.",
  "contract_version": "11.18.6"
}

Timestamps in the envelope are ISO 8601 UTC strings. History fields are omitted when unknown; a source poll that has never happened uses explicit nulls for both its timestamp and age. The server checks these shapes against the shared contract before returning a tool result.

The fields

as_of: when this answer was computed. Not when the data was recorded; when the sums were done.

recorded_since: the earliest stored history among the player sources used by this answer. Battle history can predate profile readings or signup. This is not a promise of continuous recording; check elixir_coverage before interpreting an empty period. On mixed profile/battle answers, the sources can have different start dates.

recording_active_since: when the currently active recording began. Kept separate because imported history and earlier opponent appearances can predate it. Stopping recording does not erase the stored history.

source_polls: each relevant endpoint's observed_at and freshness_seconds. Battle tools use the battle-log poll, profile tools use the profile poll, and the player summary exposes both. war_current exposes its current-race poll. A source never polled reports null values.

freshness_seconds: the oldest of those relevant poll ages, not the newest poll of any kind. It is null if a required source has never been polled. A fresh profile must never disguise a stale battle log. These fields describe polling freshness, not the age of every individual historic row.

completeness_note: present only when recording is known to be incomplete or cannot be known. On a player subject whose window ends inside the last seven days (an unbounded window ends now), the envelope reads the newest profile interval, the two latest profile polls and the recorded battles between them, and the note fires when fewer than 90% of the battles the profile counted are recorded, or when the interval is not comparable and more than 48 hours have passed since the last profile poll. It names the interval, the counts and the elixir_coverage call that has the whole week. On battles_compare it reads each compared tag, and carries one sentence per incomplete side, each naming its tag; a complete side is not mentioned. When it is there, say so in the answer. It is the service admitting a gap; an agent that drops it on the floor is laundering that admission.

timezone_applied: the display timezone used for local labels, when one applied: the account's, or the call's own timezone argument. Stored timestamps and the envelope remain UTC. Every windowed tool that resolves a date-only bound or a local label emits it (the battle tools, clans_standings, clans_participation, battles_trends, the daily series, elixir_timeline); it is absent when no zone applied, and live_fetch, feedback and changelog answers never carry it.

read_to (on elixir_timeline, beside the envelope), your read pointer after the call: the window's end when the call moved it, the pointer as it stood with mark_read: false, and null when the account has no pointer yet (nothing has been marked read); a null read_to with mark_read: false is the dry run on a fresh account, not an error.

feedback_responses_pending: maintainer replies waiting to be read with elixir_my_feedback.

timeline_pending: subjects of yours with something admitted since your timeline read pointer. A hint to call elixir_timeline rather than re-polling the data tools.

quota: your spend against both daily budgets, on every response, so a plan can be priced before it starts rather than discovered mid-sweep. calls is tool calls today (used, max, remaining); live is live fetches today. Every tool call costs one call; live_fetch, and live: true on players_profile, clans_roster, war_current, battles_query, additionally cost one live fetch, charged once, when a fresh read is queued, never for a read already in hand or for the follow-up call that finds it landed. max and remaining are null when the budget is unlimited (owner and admin). resets_at is the next UTC midnight, when both counters roll. An agent spends its owner's call budget and its owner's live lane, so every agent on one account reads the same balances. The hourly rate limit is the exception: each agent has its own hour (see Limits).

request_id: the id of the call that produced this response. Quote it when reporting an answer that looks wrong and we can find the exact row. Your own call history, with these ids, is under Settings › Usage and limits › Requests.

contract_version: the tool contract this response was produced under. If it differs from what you cached, re-read tools/list; elixir_changelog(since) says what moved.

Beside the envelope

Three keys ride at the top level of every response, next to the fields the tool is for (applied on all but six tools, which the Protocol reference names):

applied: the one echo block: what the tool actually used. window (from, to, source, timezone) on every windowed tool, plus limit, sort, mode, min_battles, segment and verbosity where they apply. window.source is argument, default, unbounded, season or fixed (and pointer on elixir_timeline); read it before you say "your last 30 days". The season-grained tools add window.season, window.crosses and window.season_age_days (Seasons).

notes: notes[], one-sentence caveats to repeat when quoting the numbers. meta.completeness_note stays in the envelope because it is about recording, not about the tool.

docs: a page#section pointer into the documentation where the formulas behind the answer live; hand it to elixir_docs({ page, section }) or read elixir://docs/<page>#<section>.

Knowing who you are connected as

initialize describes the connection twice, for two different readers.

instructions says it in prose, "YOU ACT FOR Tidewater #QRP2GV", which is the right shape for the model that reads it, and the wrong shape for the program hosting that model. That wording is tuned for the model and changes whenever the tuning does, so a client must not parse it.

The same facts ride as data, in _meta on the initialize result:

"_meta": {
  "elixir.poapkings.com/principal": {
    "kind": "agent",
    "subject": { "type": "clan", "tag": "#QRP2GV", "name": "Tidewater", "members": 47 }
  }
}

kind is person, agent or integration. subject is the clan an agent acts for, the primary player a person is, or null, and it is always present, so a misconfigured agent with no clan is something you can detect at boot rather than discover in an empty answer. A person also gets a separate clan for context; for an agent the clan is the subject.

Use it to refuse to start on the wrong kind of token, or to label a surface with what it serves. Do not use it as permission: it reports the connection you already hold, and every tool re-derives your rights server-side, so a client that lies to itself here changes nothing but its own labels.

The key is namespaced because MCP reserves unprefixed _meta keys for itself.

Honesty is a design position

The service records history from a game API that only answers "what is true right now". That means gaps are structural, not exceptional: a clan added last week has no history from last year, and a subject that stopped being polled has a stale tail.

The envelope exists so an answer can be honest about its own limits without the agent having to guess at them. Three habits worth building into any client:

  1. Caveat against recorded_since before making a claim about a period.
  2. Repeat completeness_note rather than summarising past it.
  3. Never present a recalled figure as recorded data. If the tools cannot answer, say so. elixir_coverage will tell you how complete a record is.

Coverage and result limits

elixir_coverage.observation_intervals compares a profile's lifetime-battle counter change with battles recorded in exactly (observed_from, observed_to]. Intervals can span several days and are recalculated when read, so late-arriving battles improve the estimate. A missing reading time or incompatible counter cannot produce a reliable estimate. These are estimates of recording, not proof that every battle is present.

completeness_last_7_days covers intervals ending in the last seven days; one can begin earlier. average_ratio is weighted by expected battles and excludes intervals whose ratio is unknown. It is a number, three decimals, like the interval ratio beside it. measured_intervals and unknown_intervals make that distinction visible. measured_span gives the first-to-last extent of the intervals behind the ratio and measured_hours their summed duration, so you can see how much of the week the number actually describes: a high average_ratio over a few hours describes only those hours. Compare measured_hours against 168 before reading it as a full week. incomplete_intervals counts measured intervals with fewer recorded than expected battles. Interval is_complete uses exact counts even when a rounded ratio displays as one. unmeasured_tail_hours gives the age of the unbracketed tail since the latest profile reading; it is not included in average_ratio. Older unbracketed history remains unknown.

Performance summaries aggregate the full requested time window. An explicit last_n_battles selects a recent sample; ordinary date windows have no hidden 2,000-battle ceiling.

war_current.period.source_observed_at is the latest successful current-race poll, while started_observed_at is the first sighting of that period (null until the recorder has one). The period is the calendar's and open by construction; use the source reading for what the recorder knows about the race and game_clock for the policy clock.

An MCP result over 48,000 characters of compact JSON is a structured result_too_large error ("Result is N characters; the cap is 48000."), not a cut-off success: the request was fine and the result was not deliverable. It retains its request ID, sets isError, and names the arguments that narrow the call (verbosity: "compact" is usually enough). No partial result should be interpreted as complete.

Errors

A failed call returns a structured body rather than prose:

{ "error": { "code": "not_found", "message": "…", "hint": "…" } }

The code is from a closed set, listed with the JSON-RPC and HTTP layers on the Protocol reference; the hint names the one call that would fix it. Three are worth branching on by code rather than by message: no_subject (nothing to answer about: no primary player, an unmapped on_behalf_of, an agent with no recorded clan), result_too_large (narrow the arguments; the hint sizes the page that fits) and internal (the server failed, not your call: retry once, then report the request_id). Errors carry a meta envelope too, with the same request_id: a call that failed is still a call you can ask us about.

A note for client authors: check the error body, not just the transport flag. A call can fail without a protocol-level error flag being set, and a client that only checks the flag will report confident success on a refused call. That has happened here, to us.

Help for Reading a response

Every Elixir response carries a meta envelope: when it was computed, how far back the record goes, how fresh it is, whether recording was incomplete, and the id of the call that produced it.

Read more

The page you are on rides along. Every item is answered, and the answer comes to your email and your timeline.