Start here. One call gives you the realized return over a period plus both signed measurements behind it. The rest of this page is the same data as rows, for consumers who want to do the arithmetic themselves.

The return is derived, not signed. The two endpoints are signed, which is what lets you redo the arithmetic instead of trusting ours. Both are in the response.

The one call

/feed/v1/return/<source>/<asset>.json GET · open

The realized return between two dates, with both signed endpoints. Bounds are ISO dates or epoch seconds, always UTC, both optional, and compared against each measurement's own accrual time rather than the block it was read at.

Try a date it cannot read. Put 11-08-2026 in from and run it: you get a 400 naming the problem, never a 200 carrying a figure for a different period.

Discovery

/feed/v1/index.json GET · open

Which series exist, derived from the same registry the measurement path uses. The picker above is populated from this route, so it cannot drift from what the canister can measure.

/feed/v1/health GET · open

200 when the daily round is keeping pace, 503 when it is not. No credential and nothing to expire, so an off-the-shelf uptime service can watch it. It asserts that the job ran, separately from whether a measurement succeeded.

The rows, for consumers who want them

/feed/v1/yield/<source>/<asset>.json GET · open · also .csv

The signed series, paginated. Each row carries the signed preimage as base64, so your whole integrity check is sha256(base64decode(x)) and no canonical-JSON implementation is needed anywhere. The bytes travel rather than the object, because re-serialising a parsed object produces different bytes.

The CSV of the same series carries no signature column and must never gain one. A signature without the canonical bytes and the inclusion path cannot be checked, so a consumer who tried would fail against healthy data. The absence is carried as data: verifiable = no (see JSON feed).

/feed/v1/rounds/<root>.json GET · open

One signing round, by its Merkle root. Since the 2026-08-11 cutover a round is signed once over a root, and every measurement carries the inclusion path binding it to that root. This is how you see the other leaves alongside yours.

Both signing procedures are permanent, and the envelope's presence tells you which applies. Never the date: a cost experiment left five per-measurement-signed rows sitting after the cutover, so a verifier reading the procedure off a timestamp is wrong on live data.

What a consumer needs once

/feed/v1/schema.py GET · open

The reference verifier, generated by the canister: the Merkle fold, the envelope classification and the row check, in dependency-free Python. The producer, this module and the browser are pinned to the same reference roots by test, so there is no fourth implementation of the tree to drift.

/feed/v1/README.txt GET · open

What the feed is, and a section on what it does not prove. Completeness is the honest one: every row proves itself, the list of rows is not signed, so an omitted row leaves no trace.

Who controls this, and what code is running

/trust/v1/controllers.md · /trust/v1/module-hash-history.json GET · open

A separate prefix on purpose: this is trust evidence rather than series data, so it is not reachable by walking a feed cursor. Both documents are compiled into the canister from the repository, so the bytes you read ride the module hash you are checking.

Each document points at a better authority than itself. For the controller list that is the Internet Computer's certified state, read with icp canister status, which needs no trust in this canister. And the embedded history cannot contain the deploy that shipped it, since a build's hash is unknown until it is installed, so a mismatch straight after a deploy is the expected state.

Two things to know before you build on this

Use the raw. hostname. This surface sets no IC-Certificate header, and the certified hostname answers 503 rather than serving an uncertified response, so the working host is the one in every command above.

No keys, no quotas, no rate limits, and no deprecation policy beyond the /v1/ prefix. These are queries and spend no metered resources, which is why they are open. That is the current state rather than a promise, and if it changes it will change here first.