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-allalone 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:
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:
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:
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
/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
alland1yonce and cache them for hours or days.Re-poll only
7d(and30d) 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?