For the complete documentation index, see llms.txt. This page is also available as Markdown.

API

IPOR Static API — Developer Guide

Public, read-only JSON API serving IPOR Fusion vault data, including the historical time series behind the charts on app.ipor.io.

Base URL: https://api.ipor.io

No API key. No authentication. No rate limit. No query parameters — every input is encoded in the URL path.

Important: the long-range history buckets (1y, all) are intentionally stale by days to weeks. A request for -all alone returns a series whose most recent point may be around three weeks old, with no error to indicate this. See Period buckets and freshness. This behaviour is by design and will not change; clients are expected to merge buckets.


Quick start

Chart a vault's APY over the last 30 days:

# Note the lowercase address. Anything else 404s.
curl --compressed \
  'https://api.ipor.io/v2/fusion/vault-history/1/0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2-30d' \
  | jq '.history[-1] | {blockTimestamp, apy, tvl}'
{
  "blockTimestamp": "2026-07-08T13:14:47Z",
  "apy": "3.2864762350246543",
  "tvl": "490462.27822644341523"
}

Note that the most recent point is already approximately 21 hours old. Obtaining a series that extends to the present requires additionally fetching -7d and merging the two. The reference client performs this automatically.

Discover vault addresses first:


Conventions

Addresses must be lowercase. The API is static JSON objects on S3 behind CloudFront, so paths are object keys and are case-sensitive. A checksummed (mixed-case) address returns 404.

This requires care, because /dapp/plasma-vaults-list returns most of its addresses checksummed (mixed-case). An address taken from the vault list and used unmodified in a history path will return 404. Apply .toLowerCase() before constructing any path.

Numbers are decimal strings, not JSON numbers. "tvl": "490462.27822644341523". The precision exceeds that of an IEEE-754 double. Parse them with a decimal library (decimal.js, big.js, bignumber.js). Passing them through Number() will discard low-order digits. Token amounts (totalAssets, balance) are raw base units and must be scaled by the asset's assetDecimals.

Timestamps are ISO-8601 UTC, e.g. "2026-07-09T09:14:47Z".

Every history point carries a blockNumber, which is the correct key for joining, deduplicating, and ordering series. Prefer it over the timestamp.

Errors are XML, not JSON. A miss — unknown vault, unsupported period, or a non-lowercase address — returns the raw S3 error document with content-type: application/xml:

A client that calls .json() on any non-2xx response will throw a parse error rather than surface a clean 404. Branch on response.status before parsing.

Responses are gzipped (content-encoding: gzip). Send Accept-Encoding: gzip (curl --compressed); browsers and fetch do this automatically. The uncompressed payloads are much larger — see Payload sizes.

CORS is open (access-control-allow-origin: *, GET only). The header is returned when an Origin request header is present, so browser calls work directly with no proxy.


Period buckets and freshness

Both history endpoints take a period suffix on the last path segment:

Period
Coverage

7d

Trailing 7 days

30d

Trailing 30 days

1y

Trailing 365 days

all

Full vault lifetime

Points are spaced roughly hourly.

Why the data is spliced this way

The API is not a database query layer. Every response is a pre-rendered file written to S3 and served through a CDN. This is a deliberate design decision made for security and performance: there is no query surface to exploit or overload, and reads remain fast and inexpensive at any traffic level.

The trade-off is that a file's contents are only as current as its last write, and rewriting a multi-megabyte object is costly. Consequently, the older a file's data, the less frequently it is regenerated. The 7d file is small and is rewritten frequently. The all file is large and consists mostly of immutable history, so it is rewritten rarely.

This is the reason the most recent point in a bucket lags real time by an amount that grows with the size of the bucket. Measured against vault 0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2 on Ethereum at 2026-07-09T10:00Z:

Period
Points
Newest point
Trails real time by

7d

168

2026-07-09T09:14Z

~45 minutes

30d

720

2026-07-08T13:14Z

~21 hours

1y

8,714

2026-07-06T13:14Z

~2.9 days

all

14,726

2026-06-17T13:14Z

~22 days

The exact lag varies by vault and by regeneration cycle. Do not depend on the specific values above. Depend instead on the ordering, which is invariant: 7d is always the most current bucket and all is always the least current.

This is intended behaviour and will not change. Rewriting the multi-megabyte all object every hour would provide no benefit, because its stale tail can be reconstructed inexpensively from the smaller, more current buckets. Clients are expected to perform this reconstruction.

The merge rule

To render a period accurately, fetch the requested bucket plus every more current bucket that covers its stale tail, then merge the point sets:

Requested
Fetch

7d

7d

30d

30d, 7d

1y

1y, 30d, 7d

all

all, 30d, 7d

Merge by blockNumber, sort ascending, and on a duplicate blockNumber retain the point from the more current bucket. The buckets overlap, so duplicates are expected and must be collapsed. Otherwise any aggregate computed over the series (averages, integrals, point counts) will double-count the overlap region.

Applying this, all four periods become current to within ~1 hour:

Tolerate a 404 on an individual bucket. A vault younger than a year has no 1y object, but its all and 30d objects exist. Treat the merge as successful if at least one bucket resolves. Propagate any non-404 failure rather than rendering a partial series without indication.


Endpoints

GET /dapp/plasma-vaults-list

A compact vault directory (~100 KB). Use this endpoint to enumerate vaults. It is preferable to /fusion/vaults, which is approximately 44 MB.

This is the authoritative list of vaults the API serves. Remember to lowercase address before using it in a history path.


GET /v2/fusion/vault-history/{chainId}/{vaultAddress}-{period}

Per-vault time series: APY, TVL, and allocation across markets. This is the primary charting endpoint. Subject to the merge rule.

vaultAddress must be lowercase.

marketBalances provides the data for the historical-allocation (stacked area) chart: each point records how the vault's assets were distributed across underlying protocols at that block. apy is net of fees; underlyingAssetApy and rewardsApy decompose it.

Some vaults additionally expose dexPositionBalances (LP positions, with token0/token1 USD amounts) and rewardsVestingApy. Treat all of these as optional and nullable.


GET /v2/fusion/market-history/{chainId}/{protocol}/{substrate}-{period}

Per-market time series: supply and borrow rates and market size for one market on one protocol. Use it to chart the yield of the venues a vault allocates into. Subject to the merge rule.

  • protocol — a protocol slug, e.g. aave-v3.

  • substrate — identifies the market within the protocol, lowercase. For lending pools this is the underlying asset address; for vault-shaped markets (erc4626, meta-morpho, morpho-blue) it is the market or vault address.

supplyApy = supplyBaseApy + the sum of supplyRewards[].rewardApy. Point density is at least hourly and may be finer than the vault history.

Two protocols use a substrate convention that differs from their market id: spark markets are keyed by the DAI address, and Harvest vaults use the protocol slug harvest-erc4626.


GET /fusion/vaults

The full vault and market snapshot, ~44 MB uncompressed (~4 MB gzipped). It embeds a history array within every vault object, which accounts for most of the payload size.

Do not fetch this endpoint to read a single vault. Use /dapp/plasma-vaults-list for enumeration and the -history endpoints for time series. Its principal use is the markets[] array, which provides current rates for every market on every chain in a single request. This is the sole reason the IPOR frontend retrieves it.


GET /fusion/vaults-customization-list

Atomist-supplied presentation metadata for vaults that have opted in. Only a minority of vaults appear; absence from this list is normal and not an error.


GET /fusion/markets/{chainId}/{protocol}/{marketId} (v1)

The v1 market-history endpoint. Returns market metadata alongside a points array of the same shape as v2:

v1 is limited to a trailing 30-day window. It provides no period selector and returns no data older than 30 days.

Prefer v2 for new integrations. /v2/fusion/market-history/… exposes the full history from vault creation through the {period} suffix, up to and including all. v1 remains appropriate only where a fixed 30-day window is sufficient and the merge rule is not required.


Supported chains

chainId appears in every history path.

Chain

chainId

Ethereum

1

Arbitrum

42161

Base

8453

Unichain

130

Ink

57073

Plasma

9745

Avalanche

43114

Chain support expands over time. Rather than hardcoding this table, derive the live set from GET /dapp/plasma-vaults-list → plasmaVaults[].chainId.


Protocol slugs

The {protocol} path segment takes a slug such as aave-v3, compound-v3, morpho-blue, meta-morpho, euler-v2, spark, moonwell, gearbox-v3, fluid-instadapp, pendle, or erc4626.

Do not hardcode the set. New protocols and yield-bearing assets are added continuously, so any list reproduced here becomes inaccurate over time. Derive it at runtime:

Two slugs follow non-obvious conventions: spark markets use the DAI address as their substrate, and Harvest vaults use the slug harvest-erc4626 rather than erc4626.


Payload sizes and caching

Orders of magnitude, uncompressed, for a mature Ethereum vault. Payloads grow with vault age and market count, so treat these as a guide to relative cost, not as fixed values:

Endpoint

7d

30d

1y

all

vault-history

~200 KB

~850 KB

~8 MB

~14 MB

market-history

~100 KB

~700 KB

~5 MB

~6 MB

Endpoint
Size

/dapp/plasma-vaults-list

~100 KB (~20 KB gzipped)

/fusion/vaults-customization-list

~15 KB

/fusion/markets/… (v1)

~400 KB

/fusion/vaults

~44 MB (~4 MB gzipped)

Responses are served from CloudFront and carry etag and last-modified. Use conditional requests (If-None-Match) to avoid re-downloading unchanged objects.

Cache the long buckets. An application that re-fetches -all at a short interval re-downloads approximately 14 MB to obtain data that is regenerated only rarely. The appropriate polling strategy follows from the freshness table:

  • Fetch all and 1y once and cache them for hours or days.

  • Re-poll only 7d (and 30d) at your refresh interval. These objects are small and contain all recently added points.

  • Re-run the merge against the cached long bucket on each refresh.

Revalidate the cached long buckets periodically with a conditional request rather than caching them indefinitely. Historical data is occasionally recomputed to correct defects, so an all object can change without gaining new points. See Data accuracy and corrections.


Reference TypeScript client

Dependency-free. Handles bucket merging, address normalisation, and the XML error case. Verified against the live API.

Usage


Data accuracy and corrections

Vault APY figures are not guaranteed to be exact. Two causes account for most discrepancies:

  • Strategy changes. A vault's strategy can be reconfigured over its lifetime. Historical points computed under a previous configuration will not always be directly comparable with later ones.

  • Indexing bugs. The pipeline that computes APY, TVL, and market balances is complex, and defects occur from time to time.

Corrections are applied retroactively. When a bug is identified, the affected history is recomputed and the underlying S3 objects are rewritten. A series you fetched previously may therefore differ from the same series fetched later, and the corrected values are the authoritative ones. If you cache history, treat etag and last-modified as the signal to re-read rather than assuming historical points are immutable. This applies to the long buckets as well: an all object that has not gained new points may still have been rewritten to fix older ones.

Please report anything that looks wrong. If you observe an APY, TVL, or allocation figure that does not match your own calculation or on-chain observation, report it in the IPOR Discord:

https://discord.com/invite/bSKzq6UMJ3

Including the vault address, chain id, period, and the affected timestamps or block numbers will make the issue substantially faster to diagnose. Reports from integrators are a meaningful source of the corrections described above.


Known limitations

1y and all are stale by design. Described in Period buckets and freshness. Without merging, a chart will terminate weeks before the present.

APY values may be revised. Historical points are not immutable; see Data accuracy and corrections.

Overlapping buckets contain duplicate blockNumber values. Deduplicate during the merge. Note that the IPOR frontend's own implementation does not deduplicate. This has no visible effect on a line chart but produces incorrect results for any aggregate computation, so that implementation should not be treated as a reference for numeric work.

Addresses are case-sensitive in paths but returned checksummed in payloads. Addresses obtained from the vault list will return 404 if used without normalisation.

404 responses return XML. Check response.status before parsing the body.

No pagination, filtering, or date-range selection. Buckets are retrieved in full. There is no mechanism to request a value between two arbitrary dates; fetch the enclosing bucket and slice the series client-side.

Two market-history versions coexist. v1 (/fusion/markets/…) is limited to a trailing 30-day window. v2 (/v2/fusion/market-history/…) serves the full history from vault creation, selected by the {period} suffix. Use v2 unless a 30-day window is sufficient.

No published SLA or changelog. This API serves app.ipor.io. It is public and stable in practice, but response shapes may acquire additional fields. Parse defensively: ignore unknown fields, and treat every documented field other than blockNumber, blockTimestamp/timestamp, tvl, and totalBalance as potentially null.


Provenance

Endpoint inventory derived from the IPOR webapp at origin/develop commit 6a734ab9d — principally src/config/constants.ts, src/fusion/api/vault-history/, src/fusion/api/markets/, and src/fusion/pages/list/list/usePlasmaVaultsList.ts. All shapes, sizes, status codes, freshness figures, and the merge behaviour were verified against the live API on 2026-07-09.

Figures in the freshness and size tables are point measurements against vault 0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2 (Ethereum) and the aave-v3 / compound-v3 USDC markets. They characterise the regeneration cadence; they are not guarantees.

Counts that move — how many vaults, markets, protocols, or chains exist — are deliberately omitted from this document. Query them from /dapp/plasma-vaults-list and /fusion/vaults instead

Last updated

Was this helpful?