Skip to main content
The public REST API (@inflexion/api) is a read-only, cached facade over the protocol: no wallet, no signer, no RPC key for consumers. It is framework-free node:http, matching the engine’s style, and it composes three independently-degrading backings behind one stable envelope.
This page is the guide — it explains what each route is for, the reasoning behind its backing, and gives a worked curl for every endpoint. The complete machine-readable contract lives in the API Reference tab (served verbatim at GET /openapi.json, the single source of truth).

The mental model: three backings, one envelope

The API never re-derives protocol truth. It forwards it. There are exactly three sources of data, and the routing rule is fixed: needs history or an aggregate → subgraph (consumed via this API); needs frictionless public access with no wallet and no RPC key → this API.

Subgraph [SG]

History and aggregates via GraphQL. Deployed and indexing — these surfaces are served live from the subgraph via the API. If SUBGRAPH_URL is ever unset, they degrade to a typed pending body with the exact query embedded.

Live RPC [RPC]

Reuses @inflexion/sdk DataClient / DepositorClient. The SDK owns pricing — the API never re-derives the fair rate or load stack. Works now.

Telemetry [TEL]

Reads the engine’s append-only JSONL sinks (DEMAND_LOG, COMPETITION_LOG) for the two dynamic moat halves — files, not events, because unfilled interest on-chain would break invariant I7. Works now.
The single hard rule behind backing [RPC]: the Φ-sum fair rate is computed in exactly one place — the Stylus FairValueOracle — surfaced through the SDK; the API only forwards it. See the Φ-sum for the closed form.

The response envelope — available:true vs typed pending

Every data endpoint returns one of two shapes and never throws (the OpenAPI ApiResult):
Subgraph is live. The subgraph is deployed and indexing (from redeploy block _deployBlock = 274081134, the subgraph startBlock), so every [SG] route is served live from the subgraph via the API. The dataset begins at that single redeploy block; pre-redeploy chain state is deliberately excluded.If SUBGRAPH_URL is ever unset, history degrades gracefully to a typed pending state — never an error — and that body is self-documenting: it embeds the exact GraphQL query the surface runs, so a consumer always sees precisely what will appear.
PendingReason is a closed enum:

Standing disclosures attach to the relevant surfaces

Three disclosures travel in the response body so a consumer cannot read a number without its caveat. They are never merged — /pool is a Claim-B surface; /swaps and /mm are Claim-A surfaces.
LPs are always paid with no bad debt in FULL mode — qualified: capped payoff + solvent USDC + oracle/settlement liveness.The no-bad-debt guarantee is exact only under the full clause:
  • FULL collateralization — the underwriter has posted exactly MaxIL per swap, locked for the life of the position.
  • Capped payoff — the payout is min(realized_IL, MaxIL). The cap is load-bearing: it is what makes payout ≤ collateral hold by construction.
  • Solvent USDC — the collateral numeraire (dUSDC) holds its peg and remains redeemable.
  • Oracle / settlement liveness — the price oracle stays live and settle() runs within the protocol’s settlement window.
  • No rehypothecation breach — locked collateral stays instantly liquid; it is never routed to utilization-gated venues.
Stated without these qualifiers, the guarantee is not correct.
Depositor capital is NOT guaranteed: the junior tranche is first-loss; the senior tranche is structurally protected from underwriting loss only, not systemic tail.Underwriting capital — whether supplied to the pooled cvAMM vault (Path A) or posted directly by a market maker (Path B) — is at risk:
  • Junior tranche — first-loss. Junior depositors absorb underwriting losses first and buffer the senior tranche.
  • Senior tranche — protected from underwriting loss, exposed to the systemic tail. The senior tranche is structurally shielded from ordinary underwriting loss while junior capital buffers, but it still takes the systemic tail once the junior buffer is exhausted.
No-bad-debt is a guarantee to the LP being paid (see the FULL-mode payout clause), not a guarantee that depositor or MM capital is preserved.
Structures ship from day one; the full DYNAMICS (pool-vs-MM spread as a forward-vol signal) mature with multiple competing MMs and volume. The dataset begins at the single redeploy; the off-chain telemetry halves begin at the first interaction logged.What Inflexion offers today is the architecture and the first public view into the microstructure of the DeFi LP volatility-risk premium — not yet a mature dataset:
  • Structures are present day-one. All five non-circular behavioral signals are defined and queryable from launch.
  • Dynamics mature with volume. The richer signal — the pool-vs-MM spread read as a forward-vol indicator — only sharpens once multiple market makers compete and real flow accumulates.
  • The dataset begins at the single redeploy, and the off-chain telemetry halves begin at the first interaction logged. History is served live from the subgraph via the API; if a backing (subgraph URL / RPC / telemetry) is absent, surfaces degrade to a typed pending state — never an error.

Cross-cutting behavior

Non-GET → 405 { available:false, reason:'method-not-allowed', detail:'read-only API; GET only' }. OPTIONS → 204 with CORS. Every JSON response sends access-control-allow-origin: * so the browser dApp (a different origin) can read freely.
JSON.stringify uses a replacer that coerces every bigint → decimal string. Numbers that exceed 2^53 are always strings on the wire — see decimals & units.
JSON responses send Cache-Control: public, max-age=5; the /docs HTML sends max-age=300. Internally a per-route TTL cache memoises producers; errors are NOT cached. Per-route TTLs: markets 60s, marketById 5s, pricingPreview 3s, poolState 5s, navHistory 60s, swapById 5s, loadSurface 60s, convexitySurface 300s, netGamma 60s, sigmaHistory 60s, mmFills 30s.
Handlers never throw by design; a stray bug returns 500 { available:false, reason:'internal', detail } rather than crashing the process. An unmatched route → 404 { available:false, reason:'not-found', detail:'no route for <path>' }.
All examples below use http://localhost:8088 (default PORT=8088). The hosted deployment is at https://inflexion-backend.onrender.com (REST API at /, engine at /engine); on the free hosting tier it sleeps after ~15 min idle, so the first request after idle incurs a cold start. For contract addresses, see deployments. For the env vars that wire each backing, see env vars.

System / docs routes — [STATIC]

These three are unconditional: they always answer, regardless of which backings are wired.

GET /health — which backings are live right now

The fastest way to see the API’s state of the world. Each flag is a configuration fact, not a reachability proof.
subgraph = a SUBGRAPH_URL is configured; live = an RPC is wired; telemetry.* = each sink path is configured. The example above is the canonical hosted-state: subgraph, live RPC, and both telemetry halves all on.

GET /openapi.json and GET /docs

GET /openapi.json returns the OpenAPI 3.1 document verbatim. GET /docs serves a Swagger-UI HTML shell that renders it from a CDN.
The OpenAPI doc is hand-authored — no codegen, no network — so it stays offline-CI-safe and models the available/pending discriminated union once, then $refs it everywhere. When in doubt about a field, trust /openapi.json over any prose, including this page.

Markets directory — [SG]

The catalogue of tradeable markets. Subgraph-backed and served live via the API — per-market aggregates included.

GET /markets

getMarkets runs the Q_MARKETS query. Each market row carries token0/token1/fee, durationSeconds/durationBucket, oracleToken, active, cvammEnabled, and the rollups totalSwaps/totalSettled/totalV0/totalPremium/totalPayout/pathBFills.

GET /markets/:id

getMarketById runs the Q_MARKET_BY_ID query; the route regex requires :id to be a bytes32 hex. See marketId parity for how the id is derived identically on-chain and off-chain.

Pool / vault routes

The vault surfaces. Two of the three work now over live RPC; the NAV history is subgraph-backed. All three are Claim-B surfaces — depositor capital is at risk on both tranches.

GET /pool — live vault state — [RPC], Claim B

The composite vault state via the SDK getVaultState: NAV, senior/junior assets, utilization, concentration, the two skews, and the regime.
getPoolState backs this route. Optional marketIds (CSV bytes32) narrows the composite. With no depositor client wired (no RPC) it reports rpc-unavailable; an SDK degrade is surfaced as-is rather than masked.

GET /pool/load-surface — the price-to-beat — [RPC]

The current pool-load surface — the cvAMM’s price-to-beat — via an SDK multicall. This works now, pre-redeploy, because it reads public getters, not rich events.
getCurrentLoadSurface backs this route; it carries the maturity disclaimer. This surface is what the Path-A pool charges; an MM on Path B must beat it to win the route.

GET /pool/nav-history — [SG], Claim B

getNavHistory runs the Q_POOL_DAY/HOUR_SNAPSHOTS queries. bucket=day|hour (default day).

Pricing — [RPC]

GET /pricing/preview — live fair + pool premium

The one route that prices a hypothetical position. It forwards the SDK’s current-load surface row, which already carries fairPremium and poolPremium — the API never re-derives the fair rate. See the pricing stack for the full derivation.
getPricingPreview backs this route. The geometry inputs are raw WAD bigints:
bytes32 hex
required
The market to price against. Invalid → 400 { available:false, reason:'bad-request', detail:'marketId (bytes32 hex) required' }.
bigint (WAD)
a=(Pa/P0)2a = (P_a / P_0)^2 in WAD. See the IL formula for the geometry.
bigint (WAD)
b=(Pb/P0)2b = (P_b / P_0)^2 in WAD.
bigint
The position’s MaxIL — the unit of risk, frozen at creation.
The override is applied only when all three of a, b, maxIL are supplied — a partial geometry is ambiguous, so the SDK falls back to a neutral reference. Pass all three for a position-specific preview, or none for the market-neutral reference rate.

Swaps — [SG], Claim A

The realized fills — the on-chain record of every protected position. Subgraph-backed and served live, Claim-A disclosure attached.

GET /swaps

getSwaps runs the Q_SWAPS query.
active | settled
active → isActive:true; settled → status:'SETTLED'.
address
Filter to one market maker (lowercased handler-side).
bytes32
Filter to one market (lowercased handler-side).
int (1–1000)
default:"100"
Page size.
No injection surface. The GraphQL where clause is built handler-side from validated filters, never assembled from raw user input. Unknown filter values are dropped, not forwarded.

GET /swaps/:swapId

getSwapById runs the Q_SWAP_BY_ID query; :swapId is a decimal id. A swap row carries the full economics: v0, maxIL, premium, path, mmLoadBps, cappedAtMaxIL, realisedIL, payout, pnlForMm, settlementPrice. For a worked example of these fields end-to-end, see the lifecycle and the worked example.

The five moat signals — /data/*

The first public view into the microstructure of the DeFi LP volatility-risk premium. The taxonomy is fixed. Read the five signals for the thesis and circular vs behavioral for why each is non-circular. marketId / since filters are parsed by parseFilter: marketId must be bytes32, since a unix-ts integer. Every /data/* route carries the maturity disclaimer.

GET /data/load-surface — Signal 1

The historical clearing-load over a transparent σ_ref bucketed by geometry — getLoadSurfaceHistory runs the Q_MARKET_STATE query. Distinct from /pool/load-surface, which is the live price-to-beat over RPC.

GET /data/convexity-surface — Signal 1 + 2 structural

Clearing-load over σ_ref and the pool-vs-MM spread, bucketed by width × distance × duration, excluding cap-bound fills — getConvexitySurface runs the Q_BUCKET_AGGREGATES query.
Cap-bound fills are excluded by construction — BucketAggregate accumulates non-capped fills only. A fill that hit MaxIL no longer reflects the fair convexity load, so including it would bias the surface. Q_BUCKET_FILLS returns per-fill rows for an exact median, since the subgraph store has no median operator.

GET /data/term-structure — Signal 3

The 7/30/90d points for one (width, distance) cell — getTermStructure runs the Q_TERM_STRUCTURE query; defaults width=medium, distance=mid. The signal is the slope of medianMMLoadBps across durationBucket — Path-B behavioral; Path A is duration-flat.

GET /data/demand-requests — Signal 4 (the only split surface)

getDemandRequests backs this route. This is the only intrinsically split surface: the realized half comes from the subgraph (live), the latent half from the engine’s DEMAND_LOG (live) — each with its own availability flag.
This is the demand the chain never sees: everything an LP priced but did not buy. Because invariant I7 keeps an unchosen quote off-chain, the latent half is unreconstructable retroactively — which is exactly why the engine captures it to JSONL from the first interaction. Both halves are live, each surfaced under its own availability flag.

GET /data/quote-competition — Signal 2 dynamic — [TEL]

getQuoteCompetition backs this route; it reads COMPETITION_LOG — winners AND losers — aggregated per (market × MM).
Limit → Why → Mitigation. If COMPETITION_LOG is not configured on the engine, this returns { "available": false, "reason": "telemetry-sink-absent", "detail": "COMPETITION_LOG not configured..." }.
  • Why: the chain only ever records the realized fill; who else quoted, how wide, and who widened or withdrew under stress leaves no on-chain trace by design.
  • Mitigation: set COMPETITION_LOG on the engine from the first deploy — these records cannot be reconstructed later. See env vars and the engine guide.

GET /data/net-gamma and GET /data/supply-depth — Signal 5

getNetGamma runs the Q_NET_GAMMA query and reports the protocol’s net convexity exposure; getSupplyDepth runs the Q_ACTIVE_SWAPS query and returns the raw isActive open set the API sums off-chain Greeks over (finite-differencing the deployed ILMath / FairValueOracle).

MM + sigma — [SG]

GET /mm/:address/fills — Claim A

getMmFills runs the Q_MARKET_MAKER query; :address is a 40-hex address. The Path-B underwriter’s realized book — see Path B.

GET /sigma/:token/history

getSigmaHistory runs the Q_SIGMA_HISTORY query. The σ_ref reference-volatility history per token, including SwapPriced backfill points. This is the transparent denominator every clearing-load signal is measured against — non-circular by construction.

Route-to-backing map

A consolidated map. Use GET /health to confirm the runtime state of any deployment.
These answer with real data over live RPC and JSONL sinks, no subgraph required:These surfaces read public getters and JSONL sinks, not rich events — so they predate the subgraph.
The subgraph is deployed and indexing. All [SG] routes are live, served from the subgraph via the hosted API; SwapPriced / QuoteFilled join the structural moat halves to the dynamic halves captured in telemetry.The dataset is anchored to one redeploy block (startBlock 274081134) so the moat dataset is clean from genesis. It is still young: the structures and queries are live now, but the dynamics — regimes, forward-vol distributions, dispersion — mature with MM competition (≥ 3 MMs) and volume. Track the live status on status & caveats and what’s live.

See also

API overview

The three-layer architecture, the envelope, and when to reach for the API vs the SDK vs the subgraph.

The engine

The Path-B quote relayer — the WS protocol, the SignedQuote, and the day-one telemetry sinks the /data/* routes read.

The subgraph

The entities behind every [SG] route — deployed and indexing on The Graph Studio.

Data client (SDK)

The typed client for the same surfaces — when you want SDK ergonomics instead of raw curl.