Index API
A free, versioned JSON API for the W.E.T. benchmark indices — the governed class, with published methodology and a checksum-chained settlement ledger. No account, no key, no D+1 embargo. Every published value, settlement row, constituent list, and intraday indicative is public; attribution is required.
Fields are added, never removed or retyped. A breaking change mints v2 and leaves v1 serving. Licence wet-licence/v1.
2026-09-06 MCP / wet_search_events — The coverage envelope can report `complete`, and names its reason when it cannot. What changes and what to do
2026-09-06 MCP / wet_search_events — The board says how much of each venue it actually read. What changes and what to do
MCP launch status
W.E.T. MCP access and installation are temporarily unavailable while source-rights and compliance clearances are completed. Do not connect or install the server yet. The MCP launch-status page will report when access has reopened.
MCP GET and POST calls are refused with HTTP 503 and mcp_release_held. No tools or source-derived results are exposed while the hold is active. The public benchmark REST API documented below remains a separate surface.
Endpoints
curl -s https://www.worldeventtrading.com/api/public/v1/indices
Ticker, name, class, unit, the latest published settled value, methodology URL, attribution string, and the venue disclosure for each benchmark.
curl -s https://www.worldeventtrading.com/api/public/v1/indices/wetgri
Every published settlement row from genesis through the newest row, including published constituents, plus the public intraday indicative. Each settlement row carries its checksum, previous checksum, and composition digest.
curl -s https://www.worldeventtrading.com/api/public/v1/indices \
| python3 -c "import json,sys
for i in json.load(sys.stdin)['indices']:
l = i['latest'] or {}
print(i['ticker'], l.get('date','-'), l.get('value','-'), i['latestUnavailableReason'] or '')"A null latest is a real answer: either no settlement has been published or the newest row is awaiting a restatement. A null value inside a row is a third thing again — a day with insufficient data publishes no number rather than carrying yesterday's forward.
curl -s https://www.worldeventtrading.com/api/public/v1/indices/wetgri \
| python3 -c "import json,sys
d = json.load(sys.stdin)
print('rows', d['count'], 'chainVerified', d['chainVerified'], 'superseded', d['supersededDays'])
for r in d['rows']:
print(r['date'], r['value'], 'SUPERSEDED' if r['superseded'] else 'live', r['checksum'])"Rows flagged SUPERSEDED were restated. They stay in the response because the chain runs through them — filter them out and your own verification will fail on correct data — but never quote one as a current value. Use latest, which already excludes them.
curl -s https://www.worldeventtrading.com/api/public/v1/indices/wetgri \ | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin)['checksumRecipe'], indent=2))"
The full field-by-field construction, served with the data rather than only documented here — so an implementation can be checked against the same source the engine is transcribed from.
curl -sD - -o /dev/null https://www.worldeventtrading.com/api/public/v1/indices/wetx \ | grep -i '^x-ratelimit\|^x-wet'
Rate-limit and licence headers ride on every response, including errors.
Institutional licences may cover commercial use, redistribution, support, bulk capacity, delivery-latency or availability guarantees, and settlement-reference status. They never unlock an exclusive benchmark value. Consumer WET Premium is a member-analysis product and does not grant these commercial rights.
| Ticker | Slug | Unit | Index |
|---|---|---|---|
| WETGRI | wetgri | index-level | W.E.T. Geopolitical Risk Index |
| WETFED | wetfed | basis-points | W.E.T. Fed Path Index |
| WETX | wetx | index-level | World Event Uncertainty Index |
| WETFRAG | wetfrag | index-level | W.E.T. Fragmentation Index |
Benchmarks only. Slates are the tactical class under lighter governance and are not served by the public API — see the index board.
What a key is for
Every published benchmark value above this line is free, keyless, and stays that way — a key does not unlock a value, lower a latency, or widen a licence, and no entitlement will ever reach one. That is a standing commitment with a 90-day notice period, and it is the reason the line below can be drawn honestly at all.
A key authenticates two things. One is your own account state — watchlists and saved board screens, readable and writable over REST; charging you to read your own rows would be a strange thing to sell, so a free account reaches it. MCP access remains held and an API key does not bypass that hold. The other is the normalised venue API at /api/wet/v1 — every enabled venue’s events, markets, books, trades and streams under one contract. That is work W.E.T. does rather than data W.E.T. publishes, and it is part of Premium.
The two keyless doors on the venue API are GET /api/wet/v1/health and GET /api/wet/v1/categories, so you can see the shape of the thing before paying for it. Neither returns a price. The carve-out is exactly two routes and is pinned by a test, so a third free door cannot appear by accident.
The quota below governs everything a key reaches. The benchmark endpoints are not metered per key — they are keyless, so there is no key to meter them against, and they keep the courtesy rate limit everyone gets.
Create one at /account/api. It is shown once — only a hash is stored, so a lost key is revoked and replaced, never recovered. Scopes are fixed at creation: a key cannot gain reach later, and an absent scope is denied rather than assumed.
curl -s https://www.worldeventtrading.com/api/v1/me \ -H 'authorization: Bearer wet_live_YOUR_KEY_HERE'
Returns the key's name and scopes, the account tier, and the current hour's usage. This endpoint reads the window rather than consuming it, so polling it never changes the answer it gives.
curl -s https://www.worldeventtrading.com/api/v1/watchlists \ -H 'authorization: Bearer wet_live_YOUR_KEY_HERE'
Requires read:watchlists. Each entry carries its id and how many events it holds.
curl -s -X POST https://www.worldeventtrading.com/api/v1/watchlists \
-H 'authorization: Bearer wet_live_YOUR_KEY_HERE' \
-H 'content-type: application/json' \
-d '{"name":"Fed and macro"}'Requires write:watchlists. Returns 201 and the new watchlist, or 409 naming the limit you hit.
curl -s -X POST https://www.worldeventtrading.com/api/v1/watchlists/WATCHLIST_ID/items \
-H 'authorization: Bearer wet_live_YOUR_KEY_HERE' \
-H 'content-type: application/json' \
-d '{"eventId":"kx--EXAMPLE","note":"watching for the September print"}'Idempotent. Adding an event already on the list returns 200 with added:false and changes nothing, so a retried call after a timeout is safe. Event ids come from GET /api/wet/v1/events with a read:events key.
curl -s -X POST https://www.worldeventtrading.com/api/v1/views \
-H 'authorization: Bearer wet_live_YOUR_KEY_HERE' \
-H 'content-type: application/json' \
-d '{"name":"This week, politics","query":"window=week&category=politics"}'Requires write:views. Unknown or retired filter keys come back in `rejected` rather than being silently dropped — a saved view and a pasted link always render the same board.
curl -s 'https://www.worldeventtrading.com/api/wet/v1/events?source=all&limit=25' \ -H 'authorization: Bearer wet_live_YOUR_KEY_HERE'
Requires read:events and a Premium account. Every response carries coverage state, so a partial venue read is visible as partial rather than served as if it were complete.
curl -s https://www.worldeventtrading.com/api/wet/v1/categories
This route and /api/wet/v1/health are the two keyless doors. Neither returns a price; both are here so you can check the contract before buying it.
Four refusals that mean four different things never share a status code. A missing scope returns 403 listing what the key carries and what the route needed. A key on a plan that does not include the endpoint returns 402 naming the plan — not a 401, because the key is fine. An exhausted quota returns 429 with the window reset. A malformed request returns 400 before the key is read at all.
None of those counts against your quota. Being charged for a request that was never going to be served is indistinguishable from a billing bug, so metering happens only once a request is known to be servable.
The public benchmark contract
This tier exists to be inspected and quoted. Every published settlement row, constituent list and intraday indicative is served without an account or key; attribution is required.
No benchmark-data embargo. Every published settlement row, constituent list and intraday indicative is available without authentication. A same-day settlement may still be provisional until its UTC day ends, and that state is published with the row.
Per the W.E.T. Geopolitical Risk Index (WETGRI), W.E.T. World Event Trading.
Served as each index’s attribution field, so it never has to be retyped from this page.
60 / 60s
Per client IP, per endpoint, per origin instance. Every header below is in access-control-expose-headers, so a browser client can read the quota it is being held to.
| Header | Meaning | Sent on |
|---|---|---|
| x-ratelimit-limit | Requests permitted in one window. | every response |
| x-ratelimit-remaining | Requests left in the current window, floored at 0. | every response |
| x-ratelimit-reset | Unix time in SECONDS when the window resets. | every response |
| x-ratelimit-policy | The policy as `<requests>;w=<seconds>` — currently `60;w=60`. | every response |
| retry-after | Seconds to wait before retrying. Back off on this value, not on a guess. | 429 only |
| x-wet-licence | The tier serving the response: `free`. | every response |
| x-wet-licence-version | The licence version — currently `wet-licence/v1`. | every response |
| x-wet-attribution-required | Always `true` on this tier. | every response |
Back off on retry-after rather than on a guess. The limiter runs at the origin and per instance, and responses are edge-cacheable — so treat the counters as the origin’s view of usage, not a per-client ledger. A durable per-key quota arrives with authentication.
Nothing from the published benchmark record. The v1 compatibility field withheld remains present and empty so this commitment is machine-readable as well as prose.
The public API serves the full settled series through the newest published row — never an embargoed tail or recent-only window. The checksum chain starts at the first published value, so a window would leave you unable to verify a single link. Each response carries chainVerified and the exact algorithm, so you can recompute the chain yourself in any language rather than take ours on trust. It is null, not true, when no rows are released yet — verification over an empty series is vacuous, not passing.
Verify a checksum yourself
Every settlement row carries a checksum chained to the previous row’s. Recomputing the chain is how you establish that our history has not moved since you last read it — without asking us. Below is the construction, exactly as the engine computes it. It is also served as checksumRecipe on every history response, and has been re-derived from this text alone, in another language, against every published row.
Two 32-bit unsigned accumulators run in parallel over the same string. Initialise h1 = 0x811c9dc5 and h2 = 0x01000193. For each character c of the canonical string, in order, taking c as its UTF-16 code unit (all published inputs are ASCII): h1 = ((h1 XOR c) * 0x01000193) mod 2^32 <-- this leg, and only this leg, is FNV-1a h2 = ((h2 + c) * K) mod 2^32 <-- add-then-multiply; K depends on which digest is being computed K = 0x85ebca6b for a settlement checksum. K = 0x27d4eb2f for a composition digest. Nothing else differs between them. The multiplications MUST wrap at 32 bits (exact integers masked to 0xffffffff, or Math.imul). IEEE-754 doubles lose low bits on these products and will silently produce a wrong digest.
64 bits, rendered as 16 lowercase hex characters: hex8(h1) followed by hex8(h2), both zero-padded.
slug|date|value|divisor|compositionDigest|previousChecksum -- six fields joined by the ASCII pipe '|', in this order.
| slug | The benchmark slug exactly as served: wetgri, wetfed, wetx, wetfrag. |
| date | The settlement date, ISO YYYY-MM-DD, as served. |
| value | Fixed-point decimal with exactly 6 digits after the point (JavaScript Number.toFixed(6)). A null value (an insufficient_data day) is encoded as 0, i.e. "0.000000". |
| divisor | Fixed-point decimal with exactly 9 digits after the point. A null divisor (every benchmark except WETGRI, which alone is divisor-chained) is encoded as 0, i.e. the literal "0.000000000" -- the field is never omitted. |
| compositionDigest | The 16-hex-character digest as published on the row, inserted verbatim. |
| previousChecksum | The prior row's checksum, or the literal ASCII string "genesis" on the first row of the ledger. |
Composition digest: sort the constituent ids ascending by UTF-16 code unit (JavaScript's default Array#sort), join with a single comma, and hash that string with K = 0x27d4eb2f.
- — The digest is an INPUT to the row checksum, published verbatim so the chain is recomputable. Re-deriving it requires the full constituent list. That list is public: compact WETX archives are linked from their settlement rows, while the other governed benchmarks carry their full composition inline.
- — Chain rule: row N's previousChecksum must equal row N-1's checksum, in ledger order, with no row skipped. Superseded rows are part of the chain and must be included when recomputing it -- see the restatement note.
- — The chain is tamper-EVIDENT, not tamper-proof. It proves history has not moved since you recorded a checksum; it does not prove the value was right on the day. Challenging a value is a governance matter, not a cryptographic one.
- — A settlement date is not final until its UTC day ends: a same-day re-run of the settlement script replaces that date's row (scripts/index-settle.ts), so its checksum can change until then. Same-day rows are public and carry provisional state; consumers must not treat provisional as final.
Corrections
A corrected value is APPENDED as a new row; the original stays in the ledger flagged `superseded: true`, and the corrective row carries `restates` and `restatementReason`. Both are served, because the checksum chain runs through the superseded row and dropping it would break verification. Never quote a row with `superseded: true` as a current value — `latest` already excludes them. A settlement date can therefore carry more than one row: key your cache on `checksum`, not on `date`.
A benchmark administrator that corrects a published value in public is doing the job; one that edits a number in place is not. Which is why the wrong row stays visible: a restatement you cannot see is indistinguishable from history quietly changing underneath you. Restatement policy →
Tiers
| Tier | Latency | Commercial | Price | Status |
|---|---|---|---|---|
| Free — all published benchmark data | Available when published. No benchmark-data embargo. | No | No charge, no account, no key. | live |
| Pro API | The same published data, with contracted support and latency guarantees. | Yes | $99–299 / month. | planned |
| Enterprise | The same published data, with a contracted delivery SLA. | Yes | Custom. | planned |
The benchmark class only — WETGRI, WETFED, WETX and WETFRAG, their settlement rows, constituent lists and intraday indicatives. The normalised venue API at /api/wet/v1 and the held MCP research surface are a different class and are not licensed by this table. If you are building a product on either, write to us before you ship rather than after — the terms are being drawn and we would rather draw them around a real use than a guessed one.
Both commercial tiers read planned above, and that is the honest status rather than a soft launch: this licence has not had external legal review, and the document says so and commits to that review before any paid tier is sold. We are not going to quietly take money ahead of a promise we published. In the meantime the free tier is genuinely unlimited in what it lets you read, and a commercial conversation can start today at indices@worldeventtrading.com.
Planned means not purchasable. Institutional commercial licensing and entitlement delivery are not live. To discuss future commercial or redistribution rights, support, service guarantees, bulk capacity, or settlement-reference status, email indices@worldeventtrading.com. Contact creates no entitlement.
Quoting a value in journalism, research, teaching or public commentary is always permitted with attribution, whether or not the publication is commercial. What the free tier withholds is productisation: embedding the series in a paid product, reselling it, or redistributing it as a feed.
Changes to this contract
Append-only, newest first, dated by publication rather than by deploy. A breaking change is published here before it is enforced, and an entry marked announced is one that has been said and not yet done. Nothing on this page shortens that gap retroactively.
`coverage.status` was STRUCTURALLY INCAPABLE of reporting `complete` and had returned `partial` on every call ever made. It was derived from `getWetHealth().sources`, which is built from all nine data venues against a health map holding three adapters, so six sources were `unknown` by construction and were counted as degradation. The `complete` branch was dead code and every answer carried "ONE OR MORE INGEST SOURCES WERE NOT FRESH" - a warning true of every answer, which distinguishes nothing, exactly as `resultsComplete: false` did before it. `status` is now a conjunction of two LABELLED measurements: ingest freshness for the sources W.E.T. can actually measure, and the per-venue sweep of the generation that answered (`swept`, added in the entry below), which covers the six venues the health plane cannot. A new `unmeasuredSources` names venues NEITHER plane covered - "we did not look" is now distinct from "we looked and it was cold", and `degradedSources` is limited to the latter. On `partial`, `means` names which of the three causes fired and for which venues, instead of asserting a fixed sentence. `resultsComplete` is unchanged and still always false: the board remains a bounded slice of every venue even when `status` is `complete`.
What to do
A scanner that treated `partial` as permanent noise should start reading it. `complete` means nothing failed, so an empty result is evidence rather than a suspected outage - it still means "not on this board", never "does not exist". On `partial`, read `degradedSources` (measured and cold), `unmeasuredSources` (no adapter and no sweep) and the per-venue `swept.venues[].status` to say WHICH venues your answer could not speak for. The MCP server version moves 0.6.0 -> 0.7.0 with this published schema change.
THE SAME DEFECT, ON A LIVE SURFACE. `ok` required every one of the nine data venues to be `fresh`, while only three have a health adapter, so `ok` was false and this route answered HTTP 503 unconditionally - measured 2026-09-06 19:06 UTC in production, and true of every call since the fanout venues were added. A monitor cannot tell a permanent 503 from a real outage. Each source now carries `measured`, and sources W.E.T. has no adapter for are kept out of the pass/fail arithmetic and NAMED in a new top-level `unmeasured` array instead. A source that IS measured and is stale or unknown still degrades, unchanged - in the same production reading, Kalshi was genuinely 5.8h stale behind two `rest-sync` runs stuck `running`, and that still reports. Separately, Polymarket had been permanently `unknown` because its freshness was read from a stream store that nothing writes (`lib/polymarket/stream.ts` has no importer); a successful live REST probe now counts as an observation for it, exactly as it already did for Gemini, and `details.observation` says which of the two produced the timestamp.
What to do
Alerting on a non-200 from this route becomes meaningful rather than constant. Treat a 503 as a real degradation of a measured source. Read `unmeasured` to see which venues the verdict does NOT cover - an empty `unmeasured` is the goal state, and it is not reached today: forecastex, limitless, myriad, futuur, manifold and rain still have no health adapter.
`coverage` gains `searchable` and `swept`. Until now the envelope said `resultsComplete: false` and nothing else — true of every answer, so it distinguished nothing and a caller could not tell "not listed on any venue" from "outside the slice we read". `searchable.venues` counts events on the board that answered, per venue, exactly as the `venue` argument counts them (a cross-venue event is counted under each venue it lists, so the per-venue sum exceeds `searchable.events` by the overlap). `swept.venues` is the raw read behind that board — PRE-CLUSTERING and PRE-HORIZON, so materially larger — with a per-venue `status`: `complete`, `capped` (stopped on W.E.T.'s own row or page budget, so the venue holds more), `degraded` (ended early on a deadline or exhausted retries) or `unavailable` (threw or timed out). ON `degraded` AND `unavailable` THE COUNT IS NOT A MEASUREMENT: a 0 there means the venue was not read, never that it lists nothing. The six venues W.E.T. loads through one shared path — forecastex, limitless, myriad, futuur, manifold, rain — are now reported under their own ids rather than summed under the load path, so every value the `venue` argument accepts has a count a caller can look up. Measured on one build: 11,000 Kalshi events read, 1,055 Polymarket, 200 Gemini — 200 being exactly Gemini's unpaginated page limit, i.e. a saturated read the board had been reporting as whole. No existing field changes value.
What to do
Nothing is required. Before reporting that a market does not exist, read `coverage.searchable.venues[<venue>]` — an empty result against a venue with thousands of events on the board is evidence, and against one with 80 it is a slice. Treat a `contributed` count as a measurement only when that venue reports `status` `complete` or `capped`. The MCP server version moves 0.5.0 -> 0.6.0 with this published schema change.
The MCP endpoint is under a route-wide release hold while source-rights and compliance clearances are completed. GET and POST calls are refused with HTTP 503 and `mcp_release_held` before discovery or tool dispatch, so no tool inventory or source-derived result is exposed. Earlier entries remain below as an append-only record of changes that were previously deployed; their directions to connect, re-read `tools/list`, or use a named MCP tool are suspended for the duration of this hold.
What to do
Do not connect, install, or call the MCP service. Keep any existing client configuration disabled or remove it until the MCP launch-status page says access has reopened. API keys do not bypass the hold.
Three tools ship: `wet_pause_scanner`, `wet_pause_alert` and `wet_mark_notifications_read`. All three were already described by scopes callers had consented to — `write:alerts` reads "Create, pause, and delete alert rules, and mark notifications read", and two of those four verbs did not exist on this surface. `wet_create_scanner` own allowance refusal told callers to pause what they were not reading while naming a tool only for delete, so an agent following that advice had exactly one way to comply: delete a scanner somebody wanted kept. The `unreadOnly` filter on `wet_my_alerts` made the third gap worse than cosmetic — an agent could ask for unread notifications and had no way to mark any of them read, so it re-read the same backlog on every poll forever and could never tell three new firings from the same three. No new scope is required: pausing rides `write:scanners` and `write:alerts`, whose text now matches what they actually grant. None of the three is tier-gated — stopping a thing that runs on your behalf is a safety control, never a feature of a plan.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Nothing breaks. An existing key carrying `write:alerts` or `write:scanners` gains these immediately: scopes are fixed at key creation and no new scope was added, so no key needs reminting. Re-read `tools/list` to pick them up.
A key carrying `read:scanners` gains `wet_scanner_templates`, `wet_my_scanners` and `wet_preview_scanner`; a key carrying `write:scanners` gains `wet_create_scanner` and `wet_delete_scanner`. A scanner is a standing question over the WHOLE board — an allOf of anyOf groups against a subject — re-asked on a schedule, delivering only events that NEWLY match. Until now the account tools could watch one event or alert on one event crossing a threshold, and could not express a question. Scopes are fixed at key creation, so an existing key does NOT widen: mint a new key at /account/api to use these. `wet_preview_scanner` charges 25 units against the hourly quota rather than 1, because it runs the evaluator over the entire board; every other tool here answers about one event or one row and still charges 1. How many scanners an account may HOLD depends on its plan; reading, previewing and deleting do not. The keyless floor is untouched — `wet_benchmark_value` and every published value stay free, unauthenticated and attributed at every tier, and no tool listed for an anonymous caller changed.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Nothing breaks. To use the new tools, create a key with the scanner scopes at /account/api; call `wet_scanner_templates` first for the grammar and a working definition to adapt, then `wet_preview_scanner` to see what a definition matches before saving it.
The account-scoped write tools shipped 2026-09-01 — `wet_watch_event`, `wet_unwatch_event`, `wet_create_watchlist`, `wet_create_alert`, `wet_delete_alert` — and the surfaces that describe this server were not updated with them. The 405 landing payload at /api/mcp listed six account tools while nine existed, so three account verbs were undocumented for the whole of their life. That list is now derived from the tool set itself rather than retyped, which is why this entry can state the count with confidence. To be unambiguous: presented with a valid key, this server exposes tools that CREATE AND DELETE state on the account the key belongs to. It never has for an anonymous caller and still does not — the keyless tool list is read-only, idempotent and account-free, and that is a commitment, not a current state.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. No change is required. If you built on the assumption that every tool on this server is read-only, re-read `tools/list` with your key: `annotations.readOnlyHint` is false on every tool that writes, and has been since those tools shipped.
Measured against production across 35 events and 388 rows (816 KB), 26.8% of an average payload was a value repeated VERBATIM inside the same event, and every byte of it was charged to the caller. The largest were `settlementRules` at 13.2% (gamma gives every market of an event the same description, so a 28-row event shipped one paragraph 28 times) and the withheld explanations at roughly 5% (identical sentences restated per row). So: rows now carry `settlementRulesRef` into a `settlementRuleSet` table emitted once per DISTINCT rule; and `priceWithheld`, `yesWithheld`, `movementWithheld` and `settlementRulesWithheld` carry a short CODE whose sentence is in `withheldMeans`, with `quote.qualityMeans` likewise moved to a payload-level `qualityMeans`. Both tables ship ONLY the entries that payload used. Nothing is lost and no number changes; measured through the tool afterwards, the same three events came back 67.7%, 49.8% and 4.0% smaller — that last one a Kalshi ladder whose 15 rules are genuinely all different, which is the control: where nothing repeats, nothing is saved.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Resolve one indirection. `settlementRules` is no longer inline on a row: read `settlementRulesRef` and look it up in `settlementRuleSet`. The `*Withheld` fields are now codes rather than sentences — branch on the code (`settled`, `closed-undetermined`, `venue-quotes-nothing`, `not-trading`, `venue-publishes-no-prior`, `venue-publishes-no-rules`) and read `withheldMeans[code]` for the wording. The payload states its own shape in `payloadShape`, so a client can detect the format without version-sniffing.
A contract with no trades still has a book, and W.E.T. was publishing nothing for it. The cause: Kalshi NEVER omits `last_price_dollars` — measured across 4,268 live markets, zero were absent — it sends the string "0.0000" to mean "never traded". The fallback to the resting bid used `??`, which does not treat that as missing, so the chain stopped on a placeholder. (`||` would not have helped either: a non-empty string is truthy.) The test is now numeric. Measured impact on 1,718 Kalshi markets: 394 (22.9%) go from NO probability to a published one, spread across the whole range, and ZERO rows change in any other way. One example quoted bid 0.6500 / ask 0.6600 — a one-cent book at 65 cents — and showed nothing. Gemini shares the shape and the fix. Such a row carries `quote.basis: "bid"`, so it is distinguishable from one priced off a trade.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Nothing is required, and no number you already relied on has moved. If you treated a null `yesProbability` on Kalshi as "this contract has no market", that inference was wrong for about a fifth of them and those rows now carry a price with `quote.basis` naming its source.
A new `quote` on each open row: bid, ask, mid, spread in percentage POINTS, the last trade, the `basis` the probability is measured on, and a `quality` grade. It costs no extra venue request — the book was already in the response W.E.T. fetches and was being discarded. READ `basis` BEFORE COMPARING TWO VENUES: `yesProbability` is a MIDPOINT on Polymarket, the LAST TRADE on Kalshi (its resting BID when nothing has traded), and last-then-bid-then-ask on Gemini — one field name over several quantities, and until now nothing said which. `basis` is DERIVED PER ROW, never assumed per venue: Polymarket’s number is a midpoint only while the venue quotes both sides, and on the 27.6% of open rows with no bid it is ask/2, which is reported as `venue-reported` rather than mis-labelled a mid. `quality` is the same ladder W.E.T.’s index desk marks constituents with: `wide` means the spread exceeds 10 points and the index screens would force-remove the contract. Measured 2026-09-04 across 4,280 live board rows: 2,667 good, 657 last-only, 584 wide, 372 with no book at all; in the 40-60% band, 379 of 910 (41.6%) grade `wide`, one quoting bid 1 / ask 100 and publishing 51%. wet_screen_markets gains `maxSpread` (live mode only) and reports `wideOrUnbooked` on every screen. No existing number changes.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Nothing is required; `yesProbability` is untouched. If you compare prices across venues, read `quote.basis` first. If you screen a probability band, pass `maxSpread: 10` — without it a 40-60% screen returns rows whose book is bid 1 / ask 100. `maxSpread` is refused on the dated corpus, which records no book.
The same derivation that fixed the MCP reaches the normalised venue API, because the field was hardcoded null in BOTH Polymarket mappers. A resolved Polymarket market now carries `result` — the winning outcome label, read positionally from that market’s own outcomes — where it previously always carried null, and its `status` reads `resolved` rather than `closed`, matching what Gemini has always reported for the equivalent state. No field is removed and no existing non-null value changes.
What to do
Nothing is required. Callers that branched on `status === ‘closed’` for Polymarket should note that resolved markets now report `resolved`; every consumer in W.E.T.’s own code already tested the two together. Callers that treated `result: null` as “this venue never reports results” should stop.
Every venue retires settled outcomes on its own schedule — Polymarket outside roughly 36 hours of the event, Kalshi 12 hours after each close, Gemini never — and a live read inherited that window. Measured 2026-09-03, 706 settled outcomes across 300 live Polymarket events were reachable by no call at all. `includeSettled: true` returns them, at no extra venue request: the venue already ships those rows in the same response and W.E.T. was discarding them. Two examples read live on 2026-09-04: the 2026 men’s US Open winner market goes from 28 outcomes to 48 (20 settled, 20 results derived), and the 2027 Bitcoin price ladder from 32 to 47 (15 settled, 14 results). Settled rows carry `result` and no probability, per the same-dated entry below. The default is unchanged, because the board is a live trading surface and showing last week’s resolved rungs beside this week’s open ones is a worse board.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Nothing, unless you want the settled rows. Pass `includeSettled: true` alongside `eventId`. It returns outcomes the venue has RETIRED FROM TRADING — never one that has yet to open, which is a distinction Kalshi’s status vocabulary makes and “not currently trading” does not. It is refused with `oddsSlug`, whose dated snapshot records no per-outcome settlement, and a non-boolean value is refused rather than read as false.
An outcome the venue has already settled no longer carries `yesProbability`. It carries `result` — the winning side in the venue’s own words (‘yes’, ‘no’, ‘void’, or a named outcome such as ‘Georgia Tech’) — together with `priceWithheld` saying why there is no price. Measured against 706 settled Polymarket rows on 2026-09-03, the old number was wrong on every one of them: 366 published null (the row had resolved NO, so the venue published 0, and we reported that as “no price available”), 277 published 99 (resolved YES, clamped down from 100) and 13 published 50 (a void, reading as a coin flip). On Kalshi the settled row kept its LAST TRADE, so a market that resolved `no` published 1–5 and was indistinguishable from a live longshot. wet_screen_markets now excludes settled outcomes from a price screen for that reason and reports the count in `settledSkipped`. A withheld price is now ABSENT rather than null, which is what the payload’s own `unpricedMeans` has always said it was.
What to do
Historical instruction only — do not follow it while the September 6 MCP release hold is active. Read `status` and `result`. A row with `status: ‘ended’` carries no probability; if the venue has published an outcome it carries `result`, and if not it carries only `priceWithheld` saying trading closed with no result yet — those are different states and the payload no longer conflates them (`settledHeldCount` vs `closedHeldCount`). `movement` is withheld on such rows too, because `priorProbability` plus `change` reconstructs the very quote being withheld. Code that read `yesProbability` there was reading a settlement artifact as an opinion; there is no replacement number, because there is no probability of a thing that has already happened.
Eleven of the thirteen routes under /api/wet/v1 — events, markets, search, category events, orderbooks, trades, and both streaming endpoints — require a key carrying the read:events scope. GET /health and GET /categories stay keyless so an integrator can inspect the surface before buying, and that carve-out is exactly two routes. This covers normalised VENUE data only. Every published benchmark value stays free, keyless and unauthenticated, as does the odds corpus at /api/data and the keyless floor of the MCP server — those carry a standing commitment with a 90-day notice period and this change does not touch them. The surface is part of WET Premium; a valid, correctly scoped key on a lower plan is refused 402 rather than 401, so the reason is never ambiguous. A streaming connection costs 60 quota units at admission because it does the work of that many reads.
What to do
Mint a key at /account/api with the read:events scope and send it as `Authorization: Bearer wet_…`. Keys minted before this entry do not carry the scope, because scopes are fixed at creation — mint a new one rather than expecting an existing key to widen.
What this data is not
A benchmark reading of prediction-market prices. Not a forecast, not advice, and never a performance claim. Values describe what markets priced, not what will happen and not what anyone earned. A benchmark is not something anyone can put money into: there is no fund, no product and no account behind this number, and it cannot be bought, sold or held.
W.E.T. World Event Trading is a benchmark administrator, not an exchange. Challenging a published value never requires a licence — see complaints & challenges.