> ## Documentation Index
> Fetch the complete documentation index at: https://inflexion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Public REST API overview

> The read-only cached facade over three backings (RPC, subgraph, telemetry) — its two-shape response envelope, the typed PendingReason enum, the three standing disclosures, and the cross-cutting GET-only / CORS / caching / bigint-safe contract.

The public REST API (`@inflexion/api`) is a **public, read-only, cached** facade over
the protocol's microstructure. There is **no wallet, no signer, and no RPC key for
consumers**. It is framework-free `node:http` — the same style as
[the engine](/integrate/engine) — which keeps the whole stack offline-test-friendly.

Its job is to give a browser dApp or a third-party integrator **frictionless
public access** to the data Inflexion produces, without anyone having to hold a key or
run an indexer. The chain is always the authority; the API is a convenience and data
surface that **never settles, matches, or holds a wallet**. It is hosted at
`https://inflexion-backend.onrender.com` (the REST API at `/`, the engine at `/engine`).

<Info>
  **The routing rule:** *needs history or an aggregate → subgraph (consumed via this API);
  needs frictionless public access with no wallet and no RPC key → this API.* If you are
  building a wallet-side client that already has an RPC key, prefer
  [the SDK](/sdk/overview) directly; the API exists for the keyless public path.
</Info>

## The three backings

The API composes **three backings**, each of which **degrades independently**. A failure
or absence in one never takes down the others — every surface reports its own honest
availability.

<CardGroup cols={3}>
  <Card title="Live RPC" icon="bolt">
    Reuses the SDK's `DataClient.getCurrentLoadSurface` and `DepositorClient.getVaultState`.
    **The SDK owns pricing; the API never re-derives the fair rate or load stack.** Live.
  </Card>

  <Card title="Subgraph" icon="diagram-project">
    History and aggregates via GraphQL. The subgraph is **deployed and indexing**, so these
    surfaces are **served live through this API**. If `SUBGRAPH_URL` were unset, they would
    return a typed `pending` body **with the exact query embedded**.
  </Card>

  <Card title="Telemetry" icon="file-lines">
    Reads the engine's append-only JSONL sinks (`DEMAND_LOG`, `COMPETITION_LOG`) for the
    two dynamic moat halves — **files, not chain events**, because putting unfilled interest
    on-chain would break invariant I7.
  </Card>
</CardGroup>

The deliberate split matters: the **live RPC** surfaces (pool load-surface, pool vault
state, pricing preview), the **subgraph** history/aggregate surfaces, and the
**telemetry** surfaces (latent demand, quote competition) are **all live**. Because each
backing degrades independently, if any one backing were absent only its surfaces would go
`pending` — the API is honest per-backing by construction, not all-or-nothing.

<Warning>
  **Limit → Why → Mitigation: the SDK is the only place pricing is computed.**

  * **Limit.** The API will not let you compute a fair rate from its own endpoints — it
    only forwards what the SDK returns.
  * **Why.** The Φ-sum fair rate is computed in **exactly one place**: the Stylus
    `FairValueOracle`, surfaced through the SDK. Re-deriving it in the API would create a
    second implementation that could drift from the on-chain truth — exactly the
    off-chain-reimplementation failure mode the protocol forbids.
  * **Mitigation.** The API forwards the SDK row, which already carries `fairPremium` and
    `poolPremium`. One source of truth, no drift. See
    [the published FairValue pillar](/concepts/fair-value) and
    [the Phi-sum](/math/fair-value-phi-sum).
</Warning>

## The response envelope: `available:true` vs typed `pending`

**Every data endpoint returns one of exactly two shapes, and never throws.** This is the
single contract a consumer must handle.

<CodeGroup>
  ```jsonc Available theme={null}
  { "available": true, ...payload }
  ```

  ```jsonc Pending (typed degraded) theme={null}
  { "available": false,
    "reason": "<PendingReason>",
    "detail": "human note describing what unblocks this surface",
    "query": "<the GraphQL query this surface runs>" }  // present for subgraph routes
  ```
</CodeGroup>

There is **no third shape**. A consumer branches on `available`, and on the pending
branch reads a closed enum — never a free-text error string it has to parse.

### `PendingReason` — the closed enum

`PendingReason` is a **closed enum**. Every degraded state maps to exactly one of these
five reasons, so client code can switch on them exhaustively.

| Reason | When | What unblocks it |
| - | - | - |
| `subgraph-not-deployed` | no `SUBGRAPH_URL` configured (not the current state — `SUBGRAPH_URL` is set) | configuring `SUBGRAPH_URL` |
| `subgraph-unreachable` | configured but HTTP / network / timeout failure | the subgraph endpoint coming back up |
| `rich-events-pre-redeploy` | subgraph deployed-but-quiet: `SwapPriced` / `QuoteFilled` not firing | the first priced fill |
| `telemetry-sink-absent` | `COMPETITION_LOG` / `DEMAND_LOG` not configured on the engine | wiring the engine's JSONL sink env vars |
| `rpc-unavailable` | no RPC wired, or a live RPC read failed | supplying `ARBITRUM_SEPOLIA_RPC` (or `SEPOLIA_RPC`) |

<Note>
  **Why the subgraph routes embed the query.** `subgraphPending(reason, detail,
    query)` maps a subgraph outcome to a `Pending` and **embeds the exact GraphQL query**
  that surface runs. The surface is therefore **self-documenting**: a consumer can see
  precisely what data appears, and a degraded surface is a flip back to live, not a
  rewrite. A pending surface is not a stub — it is a fully-built surface that, if its data
  source were absent, would report exactly what it is waiting on.
</Note>

A worked example of the pending body a subgraph surface would emit if `SUBGRAPH_URL` were
unset — note the embedded query:

```bash theme={null}
curl -s http://localhost:8088/markets
# → {"available":false,"reason":"subgraph-not-deployed",
#    "detail":"no subgraph url configured",
#    "query":"query Markets($first: Int = 100) { markets(...) { id token0 token1 fee ... } }"}
```

<Note>
  **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.
</Note>

## The three standing disclosures

Three standing disclosures are attached to the relevant surfaces and are **never merged**.
They map one-to-one to the protocol's separate claims. On a depositor-facing or
payout-facing surface, the disclosure travels **with the payload** so it can never be
stripped in transit.

<Tabs>
  <Tab title="Claim A — payout / swap / MM surfaces">
    `CLAIM_A_DISCLOSURE` rides every **payout, swap, and MM** surface
    (`/swaps`, `/swaps/:swapId`, `/mm/:address/fills`). The **qualifying clause is always
    present** — the no-bad-debt guarantee is never stated unqualified.

    <Warning>
      **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.
    </Warning>
  </Tab>

  <Tab title="Claim B — NAV / yield / pool surfaces">
    `CLAIM_B_DISCLOSURE` rides every **NAV, yield, and pool** surface
    (`/pool`, `/pool/nav-history`). Depositor capital is **not** guaranteed in either tranche.

    <Warning>
      **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.
    </Warning>
  </Tab>

  <Tab title="Maturity — moat-signal surfaces">
    `MATURITY_DISCLAIMER` rides every **moat-signal** surface (the `/data/*` group, plus the
    live `/pool/load-surface`). Structures ship day-one; dynamics mature with competing MMs
    and volume.

    <Note>
      **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.
    </Note>
  </Tab>
</Tabs>

<Warning>
  **Claim A and Claim B are separate claims — never combine them.** LP no-bad-debt
  (Claim A, qualified) and depositor/MM capital risk (Claim B, both tranches) are
  **distinct**. The API enforces the separation at the surface level: `/pool` is a
  Claim-B surface; `/swaps` and `/mm` are Claim-A surfaces.
  Merging them would imply depositor capital carries the LP's no-bad-debt guarantee — it
  does not. See [no-bad-debt](/concepts/no-bad-debt) and [the tranches](/concepts/tranches).
</Warning>

## Cross-cutting behavior

Every response on every route shares one contract.

<AccordionGroup>
  <Accordion title="GET-only — the API is read-only" icon="lock">
    Non-`GET` requests are rejected: `405 { available:false, reason:'method-not-allowed',
        detail:'read-only API; GET only' }`. There is no write path through
    this service — mutation only ever happens on-chain. `OPTIONS` returns `204` with CORS
    headers.
  </Accordion>

  <Accordion title="CORS open — the browser dApp is a different origin" icon="globe">
    Every JSON response carries `access-control-allow-origin: *`; the HTML
    routes (`/docs`) do too. The dApp runs on a different origin and must be
    able to read these surfaces directly from the browser, with no proxy.
  </Accordion>

  <Accordion title="bigint-safe wire format — no precision loss" icon="hashtag">
    On-chain quantities (V0, MaxIL, premiums, NAV) routinely exceed `2^53` and cannot survive
    a naive `JSON.stringify`. The API's serializer uses a replacer that coerces **every
    `bigint` → a decimal string** (`jsonReplacer`). Parse these fields with a
    bigint-aware reader, not as JS `number`. See
    [addresses & units](/sdk/addresses-units) for the decimals (`dUSDC` = 6, `dWETH` = 18,
    feeds = 8, WAD = 1e18).
  </Accordion>

  <Accordion title="Caching — short, route-tuned TTLs; errors never cached" icon="clock">
    JSON responses send `Cache-Control: public, max-age=5`; the `/docs` HTML sends
    `max-age=300`. Internally a per-route TTL cache (`TtlCache`) memoises producers, and
    **errors are explicitly not cached** — a transient RPC blip never poisons the cache.
    Per-route TTLs:

    | Route | TTL | Route | TTL |
    | - | - | - | - |
    | `markets` | 60s | `pricingPreview` | 3s |
    | `marketById` | 5s | `poolState` | 5s |
    | `swapById` | 5s | `navHistory` | 60s |
    | `loadSurface` | 60s | `convexitySurface` | 300s |
    | `netGamma` | 60s | `sigmaHistory` | 60s |
    | `mmFills` | 30s | | |
  </Accordion>

  <Accordion title="Last-resort 500 guard — the process never crashes" icon="shield">
    Handlers never throw by design. As a backstop, a stray bug returns
    `500 { available:false, reason:'internal', detail }` rather than taking down the process.
    An unmatched route returns `404 { available:false,
        reason:'not-found', detail:'no route for <path>' }`.
  </Accordion>
</AccordionGroup>

## Environment — what makes each backing live

The API reads its backings from env; absence of any one degrades **only** that backing's
surfaces to a typed `pending`, never an error.

| Env var | Default | Effect when absent |
| - | - | - |
| `PORT` | `8088` | — |
| `SUBGRAPH_URL` | set | if unset, subgraph surfaces return `pending` |
| `ARBITRUM_SEPOLIA_RPC` | set | if unset, live endpoints return `pending` |
| `SEPOLIA_RPC` | unset | fallback RPC alias |
| `DEMAND_LOG` | unset | Signal-4 latent half `pending` (read-only) |
| `COMPETITION_LOG` | unset | Signal-2 dynamic half `pending` (read-only) |

<Note>
  **Why no silent default RPC.** The live SDK clients (`DataClient` + `DepositorClient`)
  are constructed **only when an RPC url is supplied**. With no RPC, the
  live endpoints **honestly** report `rpc-unavailable` instead of silently falling back to
  a default public node and returning numbers from an unknown source. The whole stack is
  fully testable offline — mock subgraph, injected fetch, injected telemetry reader,
  pre-built SDK clients.
</Note>

## A first look — `/health` tells you what is live

`GET /health` is the one call that reports which backings are wired right now. It is
`[STATIC]` — it never depends on a backing being up.

```bash theme={null}
curl -s https://inflexion-backend.onrender.com/health
# → {"ok":true,"subgraph":true,"live":true,"telemetry":{"demand":true,"competition":true}}
```

* `subgraph` — `true` iff `SUBGRAPH_URL` is configured (not a reachability proof).
* `live` — `true` iff an RPC url is wired (the SDK clients exist).
* `telemetry.demand` / `telemetry.competition` — `true` iff each JSONL sink path is set.

The reading above is the **current state**: live RPC surfaces, the subgraph, and both
telemetry halves are all up. (On the free hosting tier the backend sleeps after \~15 min
idle, so the first request after a quiet period incurs a cold start.)

## Self-documenting: OpenAPI + Swagger UI

The endpoint contract is hand-authored OpenAPI 3.1 (no codegen, no network), so it stays
offline-CI-safe and is the **single source of truth** for the contract. It models the
`available` / `pending` discriminated union once and `$ref`s it everywhere.

```bash theme={null}
curl -s http://localhost:8088/openapi.json   # the verbatim OpenAPI 3.1 document
# open http://localhost:8088/docs             # interactive Swagger UI in the browser
```

<Note>
  The live contract is the `/openapi.json` document served by this service plus this
  documentation.
</Note>

## Worked examples — what is live now

These calls work **today** — they ride the live RPC, subgraph, and telemetry backings.
(Use the hosted base `https://inflexion-backend.onrender.com` in place of `localhost:8088`
to hit the live deployment.)

<CodeGroup>
  ```bash Live pool load-surface (RPC) theme={null}
  # the price-to-beat; reads public getters, not rich events
  curl -s 'http://localhost:8088/pool/load-surface?marketIds=0x67c4bee1ee037851fbe2a8ecfdd0b8ae3d358283e940750c268621f776479d69'
  # → {"available":true,"surface":{...},"note":"LIVE uncached pool-load surface...","disclosure":"Structures ship..."}
  ```

  ```bash Live pool vault state (RPC, Claim B) theme={null}
  curl -s http://localhost:8088/pool
  # → {"available":true,"pool":{...NAV, senior/junior, util, conc, skews, regime...},
  #    "disclosure":"Depositor capital is NOT guaranteed..."}
  ```

  ```bash Signal 4 — demand (latent + realized, both live) theme={null}
  curl -s http://localhost:8088/data/demand-requests
  # → {"available":true,
  #    "realized":{"available":true,...from the subgraph},
  #    "latent":[{"marketId":"0x...","widthBucket":"tight",...,"count":3,"previews":2,"quoteRequests":1}],
  #    "latentEnabled":true,"disclosure":"Structures ship..."}
  ```

  ```bash Signal 2 — quote competition (telemetry) theme={null}
  curl -s 'http://localhost:8088/data/quote-competition?marketId=0x...&since=1900000000'
  # → {"available":true,"competition":[{"mm":"0x...","quotes":12,"accepted":10,"rejected":2,
  #    "minLoadBps":480,"avgLoadBps":510,...}],"enabled":true,...}
  # sink absent → {"available":false,"reason":"telemetry-sink-absent","detail":"COMPETITION_LOG not configured..."}
  ```
</CodeGroup>

<Tip>
  **Signal 4 is the only intrinsically split surface.** `/data/demand-requests` returns the
  *realized* half from the subgraph (live) **and** the *latent* half from the engine's
  `DEMAND_LOG` (live), each with its own `available` flag — so even if one backing were
  absent the other half would still serve. This is the clearest illustration of
  independent per-backing degradation.
</Tip>

## Where to go next

<CardGroup cols={2}>
  <Card title="Full endpoint reference" icon="list" href="/integrate/api-endpoints">
    Every path, query parameter, backing, and return shape — the complete `/markets`,
    `/pool`, `/pricing`, `/swaps`, `/data/*`, `/mm`, and `/sigma` catalogue.
  </Card>

  <Card title="The architecture" icon="sitemap" href="/integrate/architecture">
    How the engine, the subgraph, the SDK, and this API fit around the on-chain core.
  </Card>

  <Card title="The subgraph" icon="diagram-project" href="/integrate/subgraph">
    The history/aggregate backing — the deployed, indexing source behind the embedded
    queries.
  </Card>

  <Card title="The data moat" icon="chart-line" href="/concepts/data-moat">
    The five non-circular behavioral signals these surfaces expose.
  </Card>
</CardGroup>

For the live deployment addresses these surfaces read against, see
[the deployments reference](/reference/deployments). For the full status posture —
every pending state with its why and path-forward — see
[status & caveats](/reference/status-caveats).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.