Protocol reference
The MCP surface: transport, sessions, errors and versioning.
Everything a client author needs to connect without reading the source. The tool surface itself is on Tools (generated from the registry); quotas and rate limits are on Limits; the response envelope is on Reading a response.
Transport
| Property | Value |
|---|---|
| Endpoint (personal) | POST https://elixir.poapkings.com/mcp |
| Endpoint (agent) | POST https://elixir.poapkings.com/a/<public_id>/mcp |
| Endpoint (integration, legacy) | POST https://elixir.poapkings.com/i/<public_id>/mcp; integrations use the REST API |
| MCP protocol versions | 2025-06-18 (default), 2025-03-26 accepted |
| Framing | Streamable HTTP, JSON only. One JSON-RPC message per POST. Every reply is content-type: application/json. |
| SSE | Not used. No text/event-stream, no server-initiated messages. |
| Sessions | None. No Mcp-Session-Id header is issued or required; every request stands alone. |
| Batches | Refused: a JSON array body answers HTTP 400 with JSON-RPC -32600 "Batched requests are not supported." |
| Notifications | A message without id answers HTTP 202 with an empty body. |
| Any non-POST | HTTP 405 with allow: POST and an empty body. This includes GET /mcp. |
| Malformed JSON (after auth) | HTTP 400 {"error":"invalid_json"} |
| CORS | No Access-Control-* headers and no OPTIONS handling. Browser-resident clients need a proxy. |
| Capabilities declared | tools: { listChanged: true }, resources: { subscribe: false, listChanged: false }, prompts: { listChanged: false }. No logging. Every tools/call result carries the same JSON as structuredContent beside the text block. |
public_id is 8 to 16 characters of [a-z0-9].
Authentication
Bearer only. Cookies never reach the door: /mcp, the principal doors,
/oauth/token and the discovery documents are served behind a CloudFront
behavior that forwards none. The one exception is the consent page itself,
/oauth/authorize, which is a browser page and honours the site session
(below).
| Credential | Shape | Where it works |
|---|---|---|
| OAuth access token | opaque, eat_ + 43 base64url chars |
the resource it was issued for |
| Service token (agent key) | svt_ + 43 base64url chars |
the agent's own URL |
| Integration key | svt_… with audience integration_api |
/api/v1 only; refused at every MCP door |
Refusals:
| Case | Status | Body | Header |
|---|---|---|---|
No Authorization: Bearer |
401 | {"error":"invalid_token"} |
WWW-Authenticate: Bearer resource_metadata="<issuer>/.well-known/oauth-protected-resource[<door path>]", scope="cr:read recordings:write collections:write account:write feedback:write" |
| Unknown, expired, revoked, or suspended credential | 401 | same | same |
| Valid credential at the wrong door | 403 | {"error":"wrong_resource","message":"This credential is not for <resource>.","hint":"…"} |
none |
| Tool needs a scope the token lacks | 403 | JSON-RPC error -32003 (below) |
WWW-Authenticate: Bearer error="insufficient_scope", scope="<granted + required>", resource_metadata="…" |
Every refused credential is counted (per credential, per source address, per day) and shown to the owner on Account → Connections or the agent's page, so a runtime still presenting a revoked key is visible even though it never reaches the call log.
OAuth 2.1
Discovery
GET https://elixir.poapkings.com/.well-known/oauth-authorization-server
{
"issuer": "https://elixir.poapkings.com",
"authorization_endpoint": "https://elixir.poapkings.com/oauth/authorize",
"token_endpoint": "https://elixir.poapkings.com/oauth/token",
"registration_endpoint": "https://elixir.poapkings.com/oauth/register",
"userinfo_endpoint": "https://elixir.poapkings.com/oauth/userinfo",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["cr:read", "recordings:write", "collections:write", "account:write", "feedback:write", "account:email"]
}
GET https://elixir.poapkings.com/.well-known/oauth-protected-resource (and
…/oauth-protected-resource/a/<public_id>/mcp for an agent door):
{
"resource": "https://elixir.poapkings.com/mcp",
"authorization_servers": ["https://elixir.poapkings.com"],
"scopes_supported": ["cr:read", "recordings:write", "collections:write", "account:write", "feedback:write", "account:email"],
"bearer_methods_supported": ["header"]
}
Both documents list every capability the server defines. The five
standard ones are what the 401 challenge's scope advertises and what a
client gets without asking; account:email is the exception, described
under Signing a person in. A client
that names no scope in particular is offered every standard capability,
ticked, on the consent page, where the person can untick
any of them (since 1.0.0; before it the default was cr:read alone, which
refused the feedback every agent is told to file on its own judgment). A
client that asks for less is offered the rest as unticked checkboxes, and what
you tick is added to the grant, so a person can allow feedback:write to a
client that only ever requests cr:read. The token response reports the scope
actually granted (RFC 6749 §3.3), which is how the client learns it holds more
than it asked for. Both documents are cacheable for 300 seconds. There is no revocation or introspection endpoint; a person
revokes a connection on Account → Connections.
Dynamic client registration
POST /oauth/register, no authentication.
| Field | Rule |
|---|---|
redirect_uris |
required; 1 to 5 entries; https: on any host, or http: on localhost, 127.0.0.1, [::1]; no fragment or userinfo; max 2048 chars; duplicates collapse |
client_name |
optional; control characters and <>&"' stripped, truncated to 100; empty becomes MCP client |
| anything else | ignored |
Responses: 201 with client_id (24 chars base64url), client_name,
redirect_uris, token_endpoint_auth_method: "none", grant_types,
response_types; 400 invalid_client_metadata or invalid_redirect_uri;
429 temporarily_unavailable past 20 registrations an hour from one address
or 200 a day in total. Clients are public (no secret). A registration lives 365
days from its last use.
Authorization
GET /oauth/authorize renders the sign-in page; parameters are validated on
GET and POST:
| Parameter | Rule | Failure |
|---|---|---|
client_id |
registered and unexpired | unknown client_id |
redirect_uri |
one of the registered URIs | redirect_uri not registered |
code_challenge |
required, 43 to 128 chars of [A-Za-z0-9_-] |
invalid code_challenge |
code_challenge_method |
S256 (default); nothing else |
code_challenge_method must be S256 |
scope |
space-separated; must include cr:read; unknown scopes refused; empty or absent means every capability, offered ticked at consent |
invalid_scope |
resource |
required: the exact door URL the token is for (https://elixir.poapkings.com/mcp for a person) |
invalid_target |
state |
optional; over 512 chars is silently replaced by empty | – |
PKCE is not optional. resource follows RFC 8707 and must be repeated at the
token endpoint with the same value; the token is bound to that door.
Failures render an HTML page at the authorize URL. Nothing is ever sent back to
redirect_uri as error; a client waiting only on its callback should also
time out.
Consent is a two-step page: the person enters their account email and receives
a six-digit code (15-minute life, five attempts, single use, valid only for
this flow), then enters the code beside a list of the capabilities the client
asked for. Success redirects with HTTP 303 to redirect_uri carrying code,
state (if given) and iss. Authorization codes are eac_…, single use, 300
seconds.
A browser already signed in to elixir.poapkings.com skips the email and the
code: the page says who it is signed in as, lists the same capabilities, and
one Authorize completes the consent. switch=1 on the authorize URL
forces the email step (a shared browser). A client that asks for
account:email from an account with no address on file is sent the code way
regardless, because that is where the address is recorded. Consent by code
signs the browser in to the site as well, so the next consent, and the
console, do not ask again. Nothing about the grant differs between the two
paths.
For an agent door (/a/<public_id>/mcp), only the agent's owner may consent,
and the resulting grant is for the agent, not the person. An agent that does
not exist and an agent you do not own get the same refusal.
Tokens
POST /oauth/token, form-encoded, no client authentication.
| Grant | Required fields | Errors |
|---|---|---|
authorization_code |
grant_type, client_id, code, code_verifier, redirect_uri, resource |
400 invalid_grant (used, expired, wrong client, wrong redirect, PKCE mismatch), 400 invalid_target (resource differs from the code's) |
refresh_token |
grant_type, client_id, refresh_token, resource |
400 invalid_grant, 400 invalid_target |
| other | – | 400 unsupported_grant_type |
Success: { "access_token", "refresh_token", "token_type": "Bearer", "expires_in": 3600, "scope" }.
| Lifetime | Value |
|---|---|
| Access token | 1 hour |
| Refresh token | 30 days; every refresh returns a new pair |
| Grant (family) | 90 days from consent, then re-consent |
Presenting a refresh token that has already been rotated revokes the whole grant. Refreshing never widens scope.
Scopes
| Scope | Grants | Tools that need it |
|---|---|---|
cr:read |
every read tool, live_fetch, and elixir_timeline (moving your read pointer is a bookmark, not an account change) |
all others |
recordings:write |
track or stop tracking players and clans | elixir_track_player, elixir_track_clan |
collections:write |
edit collections you own | collections_edit |
account:write |
private nicknames and end-user identity mappings | elixir_nickname, elixir_identify |
feedback:write |
file attributed feedback | elixir_feedback |
account:email |
read the account's email address at /oauth/userinfo |
no tool; see below |
Canonical order is the order above. A call to a tool outside the token's
scope answers HTTP 403 with the insufficient_scope challenge and this body:
{ "jsonrpc": "2.0", "id": 7,
"error": { "code": -32003,
"message": "The access token lacks the capability required by this tool: recordings:write.",
"data": { "required_scope": "recordings:write", "granted_scope": "cr:read",
"hint": "Reconnect this client and keep 'recordings:write' ticked on the consent page (every capability is offered, ticked, unless the client asked for less), or edit the connection's capabilities under Account -> Connections, which takes effect on the next call. Owner-issued service tokens carry every capability. Read tools, including elixir_timeline, need only cr:read." } } }
The challenge's scope is the granted set plus the missing one, so a client
can re-authorize with exactly that value.
If your client does not implement that step-up - several do not, and some render the 403 as an expired credential - you have two ways in, neither of which needs the client to cooperate:
- At consent: reconnect and keep the capability ticked on the consent page, which offers every scope, ticked unless the client asked for less.
- After the fact: Account -> Connections lists every live OAuth connection with its capabilities and lets you change them. This covers the personal door and every agent or integration door you own, each of which carries its own grant. An agent can also connect with its service key rather than OAuth, and a key's capabilities are not part of any grant - edit those on the agent's own page (Account -> Agents). Editing takes effect on that connection's next call - no reconnect, because the token's scope is read from the grant on every request. Narrowing works the same way, so a capability can be taken back without disconnecting the client.
Either way the grant is yours; the client cannot ask on your behalf. Service tokens issued with no scope carry every capability.
Signing a person in with Elixir
A web product in the Elixir family (Elixir Clan, Elixir Drop) can use Elixir
as its sign-in. The grant it needs is the ordinary one plus account:email,
the one capability that is never offered unasked: it does not appear ticked
on the consent page, is not part of the default grant, is never widened into
from a checkbox or from Account → Connections, and is not advertised in a 401
challenge. A client that names it in scope sees it listed with the rest on
the consent page, and the person can decline the whole connection.
GET /oauth/userinfo with a bearer access token whose grant carries it:
{ "sub": "<stable account id>", "email": "you@example.com", "email_verified": true, "kind": "person" }
email_verified is always true: the person proved the address with a code
before any grant existed, and consent records the address on the account.
Refusals: 401 invalid_token (no bearer, or not a live personal-door token),
403 insufficient_scope with WWW-Authenticate: Bearer error="insufficient_scope", scope="account:email",
and 404 email_unavailable for an account that predates Elixir keeping the
address and whose holder has not signed in since (the next consent fills
it). The answer is never cached. It is the only place an account's address
leaves Elixir.
Principals and what each sees
initialize returns the same shape for every door; kind decides the tool
list.
| Kind | Door | Hidden tools | Tools listed |
|---|---|---|---|
person |
/mcp |
none | 55 |
agent |
/a/<public_id>/mcp |
elixir_my_players, elixir_track_player, elixir_track_clan |
52 |
integration |
/i/<public_id>/mcp |
the three above plus elixir_nickname, elixir_timeline |
50 |
The counts are generated from the registry at build time. Hiding is
enforced: calling a hidden tool answers JSON-RPC -32601 with data.kind
and a hint. Do not rely on a cached tools/list from another kind of
connection.
Tools are declared in group order, then by title, so a client that keeps
server order shows the domains: Account, Players, Battles, Cards, Clans, War, Rankings, Collections, Live, Timeline, Service, Help.
The group rides each tool's title
(Players · Player profile); names never carry it, so regrouping breaks
nothing. One page per group is under Tools.
initialize
{ "protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": true },
"resources": { "subscribe": false, "listChanged": false },
"prompts": { "listChanged": false } },
"serverInfo": { "name": "elixir-mcp", "title": "Elixir MCP - Clash Royale history, recorded",
"version": "3.12.0+tools.3f1c9a2b7d4e", "websiteUrl": "https://elixir.poapkings.com/" },
"instructions": "…",
"_meta": { "elixir.poapkings.com/principal": { "kind": "person",
"subject": { "type": "player", "tag": "#20JJJ2CCRU", "name": "King Thing" },
"clan": { "tag": "#J2RGCRVG", "name": "POAP KINGS" } } } }
instructions is prose tuned for the model (it names your primary, alts,
friends and clan, or the clan an agent acts for, then states the argument
conventions below once, then where to start) and changes without notice;
never parse it. The same facts ride in _meta as data; see
Reading a response.
Resources and prompts are declared beside tools since 1.0.0, because a
stateless server cannot push notifications/tools/list_changed and clients
list resources lazily at read time, so the documentation stays reachable when
a cached tools/list is stale:
| Method | Returns |
|---|---|
resources/list |
elixir://docs (the index), elixir://docs/<slug> for every page, elixir://examples, elixir://examples/<slug>, elixir://changelog, elixir://updates, elixir://cards |
resources/templates/list |
elixir://docs/{slug}, elixir://docs/{slug}#{section} (one H2 section), elixir://examples/{slug} |
resources/read |
Markdown for pages, sections and examples; JSON for the indexes, the changelog, the updates and the card catalog. An unknown URI is JSON-RPC -32002 |
prompts/list, prompts/get |
the eleven examples as prompts, each a user message carrying the example's question and the tools it uses |
Reading a resource spends no daily quota; the hourly rate limit still applies.
Versioning and the cache-buster
serverInfo.version is <contract_version>+tools.<fingerprint>, where the
fingerprint is the first 12 hex characters of the SHA-256 of the declarations
your kind of connection receives. The server is stateless and never sends
notifications/tools/list_changed even though it declares listChanged;
compare serverInfo.version on every initialize and re-read tools/list
when it differs. elixir_changelog({ since: "0.35.0" }) lists what moved,
newest first, with tools_added and breaking where relevant. Pages contain
up to 20 releases. Pass next_offset as offset, keeping since unchanged,
until next_offset is null; total counts all releases matching since.
Omit since to page through the complete history, or read the complete JSON
resource at elixir://changelog.
The contract is semver over the tool surface, not the code: additive is a
minor, breaking is a major with a deprecation window. Every response carries
meta.contract_version.
Errors
Three layers, each with a closed set.
HTTP: 401, 403, 405, 429 as above; 400 for malformed JSON or a batch; 500 never carries internals.
JSON-RPC (error.code):
| Code | When | HTTP |
|---|---|---|
-32600 |
not a JSON-RPC 2.0 object, or a batch | 400 |
-32601 |
unknown method, or a tool hidden from this principal kind | 200 |
-32602 |
unknown tool name, or unknown prompt | 200 |
-32002 |
unknown resource URI | 200 |
-32029 |
daily tool-call quota reached; message names the cap and "It resets at midnight UTC." | 200 |
-32003 |
insufficient scope | 403 |
Tool result: a failed call is a normal tools/call result with
isError: true whose content[0].text is JSON:
{ "error": { "code": "not_recorded", "message": "…", "hint": "…" },
"meta": { "as_of": "…", "request_id": "…", "disclaimer": "…", "contract_version": "3.12.0" } }
| Code | Meaning |
|---|---|
invalid_tag |
input failed tag normalisation; hint states the rule |
not_entitled |
the caller lacks entitlement to the subject (clan tools, slots, identity binding) |
not_recorded |
the subject is valid but nothing has been recorded for it |
not_found |
unknown to the record and to the live API; an unknown docs page, example or collection |
no_subject |
nothing to answer about: no primary player on the account, an on_behalf_of nobody has mapped, an agent with no recorded clan. The hint names the one call that fixes it (elixir_track_player, elixir_identify, or pass the tag) |
quota_exceeded |
a per-account slot or live-fetch cap; the daily call quota uses -32029 instead |
live_unavailable |
the live lane is not configured, or the fresh payload was refused at admission |
live_pending |
live: true found no read inside the API's cache window and queued one; nothing is recorded for the subject yet, so there is no answer to give now. The hint carries retry_after_s (1.7.0) |
bad_request |
structurally invalid input other than tags: unknown enum, inverted window, over-max limit, bad cursor, unknown timezone |
result_too_large |
the request was fine and the result exceeded the delivery cap; the hint names the narrowing arguments. Also what live_fetch answers for a battle-log path, before spending the lane |
query_timeout |
an analytical read exceeded its cancellable query budget; no analytical result is returned. Retry the named call after a few seconds or narrow its from/to window; report meta.request_id if it persists. |
Every code is one branch: the message is for a person, the hint names one
executable next step (a tool and its arguments), and an agent should never
have to read the message to know which case it is in. Check the error body,
not only the transport flag; meta.request_id identifies the call for a
report.
The heavy MCP reads battles_meta_decks, battles_meta_cards and
clans_standings share a query budget of at most 18 seconds per call,
shortened if Lambda has less time remaining. It covers the whole aggregation,
not a fresh allowance for each query. PostgreSQL cancels the work before the
25-second Lambda ceiling so the tool can return query_timeout, a retry hint
and meta.request_id, and audit the failed call. This is not a latency promise:
a full-season corpus read can still exhaust the budget under load. Smaller
limit values trim output, not the population scanned; narrow from/to to
reduce work.
The response cap
A result over 48,000 characters of compact JSON is not delivered sliced.
The body is replaced by a result_too_large error, "Result exceeds 48000
characters.", with a hint naming the tool's narrowing arguments and, where
the tool has it, verbosity: "compact" (or, for a tool without any, asking
you to report the request_id). The original request_id is preserved and
isError is set. battles_query refuses limit above 25 unless
verbosity: "compact" for this reason, and live_fetch refuses a
/players/{tag}/battlelog path with the same code before spending a live
fetch, pointing at battles_query({ live: true }).
Argument conventions
The same conventions hold on every tool; the initialize instructions state
them once and the per-argument descriptions are one line each. The shorter
version, with the tool map, is Choosing a tool.
What omitting an argument means depends on the tool's family, and the first sentence of every description says which:
| Family | Omitted argument | Means |
|---|---|---|
Player tools (players_*, battles_query, battles_performance, battles_decks, battles_cards, battles_opponents, battles_levels, elixir_coverage) |
player_tag |
the caller: a person's primary player, or whoever on_behalf_of maps to on an agent connection |
Clan tools (clans_*, war_current, war_history, war_rivals) |
clan_tag |
the recorded clan: a person's first tracked clan, an agent's clan |
Segment tools (battles_meta_decks, battles_meta_cards, battles_trends, cards_synergy, badges_rarity, badges_holders) |
the whole segment object |
the entire recorded corpus |
game_clock, cards_catalog, the Help tools |
nothing to omit | no subject at all |
No default is ever looked up first, and there is no "no default" guess: a
player tool with nothing to answer about is no_subject with the fixing call
in its hint.
- Tags.
#plus 3 to 12 characters from0289PYLQGRJCUV. Input is trimmed and upper-cased, a missing#is added, and the letter O folds to zero. Anything else isinvalid_tag.*_tagis one tag,*_tagsan array,collectiona collection's slug. on_behalf_of(≤200 chars, opaque) selects the end user on an agent connection; ignored on a personal one. An emptyplayer_tagis refused as a caller bug, never treated as "default". See Agents.- Validation is strict since 0.39.2: arguments are checked against the
published
inputSchemabefore the handler runs. An unknown enum value, an inverted date window, or alimitabove the declared maximum isbad_request, never clamped or emptied. - Windows are
from(inclusive) andto(exclusive) on every windowed tool: an ISO instant, orYYYY-MM-DDresolved in the account's timezone, where a date-onlytocovers that whole day.daysandweeks(andseasonsonwar_history) are sugar forfrom. The per-tool defaults are on Time and clocks. timezoneon any windowed tool is an IANA zone for that call alone: it resolves the date-only bounds and every local label (battle_time_localis ISO 8601 with its offset). Default: the account's timezone. An unknown zone isbad_request.appliedis the one echo block on every response:window(from,to,sourceofargument|default|unbounded|season|fixed,timezone, and on the season-grained toolsseason,crossesandseason_age_days),limit,sort,mode,min_battles,segment,verbosity, as used. There are nofilters_applied,window_*orlimit_appliedkeys.verbosity: full | compactis the one size control, onbattles_query,war_current,clans_roster,battles_levels,players_collectionandcards_catalog; each description says whatcompactdrops. There is no other flag for size.notes[]anddocsride every response: one-sentence caveats to repeat, and apage#sectionpointer into this documentation (elixir_docs({ page, section })orelixir://docs/<page>#<section>). Formulas live in the docs, never in a note.- Timestamps in responses are ISO 8601 UTC with a trailing
Z;meta.timezone_appliednames the display zone when local labels were used. Events carrycreated_at. - Null is unknown, never zero: a source never polled has
observed_at: nullandfreshness_seconds: null; an unknown war attendance isnull; a destroyed tower is0and unreported tower data isnull. - Cursors.
battles_queryreturnsnext_cursor(nullmeans the end). Treat it as opaque: pass it back unchanged, never parse or construct one; a forged or stale cursor isbad_request.elixir_timelineuses instants:from/to, andnext_cursoris the window end you just read. live: trueonplayers_profile,clans_roster,war_current,battles_queryand the board tools asks for a read no older than the API's cache. Asynchronous (1.7.0): fresh if in hand, otherwise queued and answered now from the record withlive_status: { state: "pending", retry_after_s }; call again after that. The tools are annotatedopenWorldHint.
Identifiers the record uses
deck_hash: SHA-256 hex ofsort(cards.map(c => id + ":" + (evolutionLevel ?? 0))).join(",") + "|" + (towerTroopId ?? 0). Card form (evolutionLevel1 = Evolution, 2 = Hero, absent or 0 = base) is part of identity; card levels and star levels are not.battle_id: SHA-256 of the canonical battle time, the sorted participant tags, and the battle class; the same battle seen from two logs is one row.request_id: a UUID minted before the tool runs and stamped intometa; the same id appears on Account → Activity.
Feedback and the changelog, over the wire
elixir_feedback({ message, category?, context?, request_id? }):message1 to 4000 chars;categoryone ofgeneral(default),bug,data_quality,feature,praise,other.request_idis themeta.request_idof the call the feedback is about — every response carries one, and passing it attaches that exact request, its arguments and its answer to the report, so the maintainer sees what you saw.contextstays free text for naming a tool or a question. Never metered.elixir_my_feedback({ limit?, offset?, status?, since? }):statusone ofnew,seen,planned,done,declined; returnsresponse,responded_at,shipped_in,related_tools. Pages are bounded by delivered size as well aslimit, so follownext_offsetwith the same filters until it isnull;totalcounts the filtered ledger. Only replies delivered on that page are marked seen, andmeta.feedback_responses_pendingstays raised while another response remains unread.elixir_changelog({ since?, limit?, offset? }): entries{ version, date, summary, tools_added?, breaking? }, newest first.limitis 1 to 20 (default 20);offsetdefaults to zero. Follownext_offsetwith the samesinceto reach every entry;nullmeans the end.totalis the number matching the exclusivesincefilter.
Machine-readable surfaces
Over the connection itself, three read-only Help tools serve this
documentation from the same sources the site renders: elixir_docs (the
index; one page by slug; one H2 section with page + section; or a word
search with query), elixir_examples (the eleven worked examples with the
tools each calls) and elixir_updates (What's new, newest first, since a
date). The same text is reachable as resources at elixir://docs,
elixir://docs/<slug>, elixir://docs/<slug>#<section>, elixir://examples,
elixir://examples/<slug>, elixir://changelog, elixir://updates and
elixir://cards, and the examples as prompts. Over plain HTTPS:
| URL | Contents |
|---|---|
/llms.txt |
this site in one page: docs index, tool list, endpoint |
/llms-full.txt |
every documentation page and the full tool reference in one fetch |
/tools.json |
the registry as JSON: name, group, description, arguments, required scope |
/docs/integration-api.json |
the REST API's OpenAPI 3.1 document |
/updates |
every shipped change, contract versions included - the same list elixir_changelog returns, with the releases around it |
/feed.xml |
RSS 2.0 of What's new |
/api/public/stats, /api/public/status |
live corpus totals and recording health, no authentication |