GET /v1/relations/{left_venue}/{left_id}/{right_venue}/{right_id} returns the proof behind the relation in the latest graph: both contract versions with the structured terms the proof read, the steps in the order the graph applies them, the evidence recorded when the relation was first found, and the runs that found and last confirmed it. The steps are rebuilt from the stored contract versions with the same code that builds the graph, so an explanation can never say more than the graph does.
Reading a trail
Each contract
Step kinds
Each step has astep kind, what each side contributed as left and right, what it established as result, and where it helps a detail in plain words. relation appears only on the conclusion.
Steps are emitted only where they apply: a within-venue ladder has no
game_quantity; a cross-venue pair of the same event has no same_event. A step with a negative result ends the argument for that path, and the trail may still conclude by another (a cross-venue pair in one merged event, for instance).
Reproduced or not
reproduced is true when running the graph’s code over the stored contract versions of both markets and every market of their events yields the stored relation. It is false when it does not, usually because the venue changed metadata outside the rules hash since the relation was found. The trail then ends with:
Not found
relation_not_found is the answer for two unrelated markets, and also for two markets that were related until one of them got a new contract version the graph has not yet related. GET /v1/markets/{venue}/{id}/relations lists what the latest graph does relate a market to.
Example: a ladder within one venue
Two strikes on the same Kalshi spread event: “Aberdeen wins by more than 2.5 goals” and “by more than 1.5 goals”.rules, yes_label, no_label and the right side’s event_id are returned in full.
Example: a sports pair across venues
The 1.5-goal strike above against Polymarket’s “Spread: Aberdeen FC (-1.5)”:orient_margin would report negated with the flipped interval on the right, and the intervals step would compare (1.5, ∞) with (−∞, −1.5): contradicts. A total (over/under) pair has no orient_margin step at all, because a total has no side.
The same_game detail is worth reading: the venues match on 90 minutes plus stoppage time, but “postponement and cancellation settle differently”, which is why a lead on such a pair carries an issue.
Example: an economics pair across venues
A Kalshi strike ladder and a Polymarket bucket question on one economic or crypto variable (a Fed target-range bound, a price of Bitcoin or Ether) take a different path, since Polymarket carries no structured strike there and its range is read from the question. The steps are, in order:settlement.scope then reads settles on <source>, observed at <time>, and the same_variable detail says how the two observations line up. When a bucket’s inclusivity is only implied by the question, the intervals step passes only if the relation holds under every convention; otherwise its result is ambiguous and there is no conclusion.
At the time of writing the latest graph contains no economics pair across venues (every one of its 4,400 cross-venue pairs is a sports line), so the fragment above shows the shape the code produces rather than an observed response. Pairs appear as soon as both venues list matching contracts.
Related
- Relations: what each relation means and how one is proven.
- Cross-venue pairs: finding pairs to open up.
- Rules: the contract versions a proof is pinned to.
- Live stream: every new relation arrives with its
proof_path.