Skip to content
This repository was archived by the owner on Jul 21, 2026. It is now read-only.

Commit 33700fc

Browse files
committed
docs: dynamic loading of metered plans and weighted crediting from computes package catalog
1 parent 49b2737 commit 33700fc

4 files changed

Lines changed: 242 additions & 61 deletions

File tree

docs/agents-and-bds/metering-and-api-keys.md

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ title: Metering & API Keys
44
---
55

66
import LiveCreditPlans from '@site/src/components/LiveCreditPlans';
7-
import LiveTimeseriesMultiplier from '@site/src/components/LiveTimeseriesMultiplier';
87

98
# Metering & API Keys
109

@@ -200,43 +199,24 @@ The hosted MCP server does not apply a separate charge layer. It forwards your B
200199

201200
A balance at or below zero causes the resolver to return `402`. The hosted MCP server propagates this back to the MCP client. Scripts in `powerloom-bds-univ3` call `get_credit_balance` before each recipe run to surface this before it becomes a mid-run 402.
202201

203-
### Debit formula
202+
### How much each call costs
204203

205-
Pricing is **not** one flat rate for every `/mpp/` route.
204+
Pricing is **not** one flat rate for every `/mpp/` route. Your balance is debited when the resolver accepts a metered request. Per-route **`credit_weight`**, stream session rates, and time-series lookback tiers all come from one place:
206205

207-
1. **Catalog**[`snapshotter-computes` `api/endpoints.json`](https://github.com/powerloom/snapshotter-computes/blob/bds_eth_uniswapv3_core/api/endpoints.json) lists every metered `/mpp/...` route with a **`credit_weight`** (catalog `version` 2+). That file is the product definition for per-route cost multipliers.
208-
2. **Core API** — Before serving the request, the resolver matches your path to a catalog template and sends `route_template` + `credit_weight` to the metering service (`POST /internal/billing/deduct`). Optional header `X-BDS-Client-Source` (`cli`, `mcp`, `direct`) is stored for usage reports only.
209-
3. **Metering service** — Applies env base rates only; it does **not** choose per-route weights:
210-
- **`/mpp/stream/...`** → flat **`CREDIT_PER_STREAM_SESSION`** per connection (SSE; `credit_weight` is ignored).
211-
- **`/mpp/timeSeries/...`****`CREDIT_PER_EPOCH × credit_weight × lookback_multiplier`**. The base `credit_weight` is **5**; the resolver adds a **lookback multiplier** (sent as `history_multiplier`) based on the `time_interval` window — see the [Time series lookback multiplier](#time-series-lookback-multiplier) table below.
212-
- **All other metered GETs****`CREDIT_PER_EPOCH × credit_weight`** (default weight **1** if the catalog match is missing).
206+
**[Endpoint Catalog — Credit weights](/bds-data-market/endpoint-catalog#credit-weights)** (live table from [`api/endpoints.json`](https://github.com/powerloom/snapshotter-computes/blob/bds_eth_uniswapv3_core/api/endpoints.json))
213207

214-
The default **`CREDIT_PER_EPOCH`** on the metering deployment is **`10/7200`** credits per request at weight **1** (roughly **1 credit ≈ 720** epoch-scoped GETs at that default). Top-up **plans** (`GET /credits/plans`) sell credit bundles; they are separate from per-route weights.
208+
**Credit plan (summary):**
215209

216-
**Example weights** (see the live catalog for the full table):
210+
- **Typical GET (weight 1):** **1/7200** of a credit per request — about **7200** such calls per **1** credit.
211+
- **Heavier routes:** multiply that base by the route’s catalog weight (e.g. weight **10****10/7200** credits per call).
212+
- **SSE stream** (`/mpp/stream/allTrades`): **0.01** credits **per connection** when the stream opens (not per event on the wire).
213+
- **Time series** (`/mpp/timeSeries/...`): weight **5** × a **lookback multiplier** by window — see [Endpoint Catalog → Time series lookback](/bds-data-market/endpoint-catalog#time-series-lookback-multiplier).
217214

218-
| Route pattern | `credit_weight` | Debit (at default `CREDIT_PER_EPOCH`) |
219-
|---------------|-----------------|--------------------------------------|
220-
| `/mpp/snapshot/...`, `/mpp/dailyActivePools`, … | 1 | 1× base |
221-
| `/mpp/ethPrice` | 5 | 5× base |
222-
| `/mpp/ethPrice/{block_number}` | 10 | 10× base |
223-
| `/mpp/token/price/...` (latest) | 5 | 5× base |
224-
| `/mpp/token/price/.../{block_number}` | 10 | 10× base |
225-
| `/mpp/timeSeries/...` | 5 × lookback | 5× base × lookback multiplier (see below) |
226-
| `/mpp/tokenPrices/all/...` | 10 | 10× base (USD Price Feed) |
227-
| `/mpp/stream/allTrades` || flat stream session rate |
215+
**Pulse trader (`bds-agent trade run`):** **`GET /mpp/stream/allTrades`** for the session, plus **`GET /mpp/tokenPrices/...`** each epoch with default `--price-source usd`. No separate Pulse or CLI fee — only catalog-priced routes.
228216

229-
There is **no** separate SKU for “running Pulse” or “using the agent CLI” — premium usage shows up as higher-weight routes (especially **`/mpp/tokenPrices/all/...`**) plus stream debits. Monitor spend with **`bds-agent credits usage by-endpoint`** or the [Usage Dashboard](#usage-dashboard) **Top endpoints** table.
217+
**YAML / MCP agents:** many recipes use **`bds_stream`** → the same stream endpoint; you pay the session rate above, not per SSE message.
230218

231-
### Time series lookback multiplier
232-
233-
`/mpp/timeSeries/...` is the one metered route whose cost depends on a **query parameter**, not just the static catalog weight. On top of its base `credit_weight` of **5**, the resolver applies a **lookback multiplier** keyed off the `time_interval` value (how far back the window reaches), so the debit is **`CREDIT_PER_EPOCH × 5 × multiplier`**. This reflects how many historical snapshots the resolver walks to serve the window — for real-time pricing, per-block `/mpp/token/price/.../{block_number}` is cheaper and more precise.
234-
235-
The tiers below load live from the resolver endpoint catalog (`billing_modifier` on the time series route), so they always reflect the deployed policy:
236-
237-
<LiveTimeseriesMultiplier />
238-
239-
For example, `/mpp/timeSeries/.../3600/144` (1-hour lookback) costs 5 × 4 = **20×** the base epoch rate; a 24-hour window costs **640×**. The multiplier keys off `time_interval`, not `step_seconds`.
219+
Monitor spend with **`bds-agent credits usage by-endpoint`** or the [Usage Dashboard](#usage-dashboard) **Top endpoints** table. [Plans](#plans-and-credit-balance) above are how you buy more credits.
240220

241221
## Usage and account dashboard
242222

docs/bds-data-market/endpoint-catalog.md

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ title: Endpoint Catalog
44
---
55

66
import LiveTimeseriesMultiplier from '@site/src/components/LiveTimeseriesMultiplier';
7+
import LiveMeteredRoutesTable from '@site/src/components/LiveMeteredRoutesTable';
78

89
# Endpoint Catalog
910

@@ -34,44 +35,21 @@ For public integrations, treat `/mpp/...` as the canonical consumption path. Som
3435

3536
## Credit weights
3637

37-
Metered routes are **not** billed at one flat rate. Each entry in [`api/endpoints.json`](https://github.com/powerloom/snapshotter-computes/blob/bds_eth_uniswapv3_core/api/endpoints.json) carries a **`credit_weight`** multiplier, and the resolver (core-edge full node) debits **`CREDIT_PER_EPOCH × credit_weight`** per request before serving it. SSE streams bill a flat session rate (`CREDIT_PER_STREAM_SESSION`; weight ignored), and an unmatched route defaults to weight **1**. See [Metering & API Keys → Debit formula](/agents-and-bds/metering-and-api-keys#debit-formula) for base rates and worked examples.
38-
39-
The weights below mirror catalog **version 2** for the BDS Uniswap V3 market; `api/endpoints.json` is the single source of truth — verify against the live file before relying on a value.
40-
41-
| Metered route | `credit_weight` |
42-
|---------------|-----------------|
43-
| `/mpp/pool/{pool_address}/metadata` | 1 |
44-
| `/mpp/token/{token_address}/pools` | 1 |
45-
| `/mpp/ethPrice` | 5 |
46-
| `/mpp/ethPrice/{block_number}` | 10 |
47-
| `/mpp/token/price/{token_address}/{pool_address}` | 5 |
48-
| `/mpp/token/price/{token_address}/{pool_address}/{block_number}` | 10 |
49-
| `/mpp/snapshot/base_all_pools/{token_address}` | 1 |
50-
| `/mpp/snapshot/base/{pool_address}` | 1 |
51-
| `/mpp/snapshot/base/{pool_address}/{block_number}` | 1 |
52-
| `/mpp/snapshot/trades/{pool_address}` | 1 |
53-
| `/mpp/snapshot/trades/{pool_address}/{block_number}` | 1 |
54-
| `/mpp/snapshot/allTrades` | 1 |
55-
| `/mpp/snapshot/allTrades/{block_number}` | 1 |
56-
| `/mpp/stream/allTrades` | flat stream session rate |
57-
| `/mpp/tokenPrices/all/{token_address}` | 10 |
58-
| `/mpp/tokenPrices/all/{token_address}/{block_number}` | 10 |
59-
| `/mpp/tradeVolumeAllPools/{token_address}/{time_interval}` | 1 |
60-
| `/mpp/tradeVolume/{pool_address}/{time_interval}` | 1 |
61-
| `/mpp/poolTrades/{pool_address}/{start_timestamp}/{end_timestamp}` | 1 |
62-
| `/mpp/timeSeries/{token_address}/{pool_address}/{time_interval}/{step_seconds}` | 5 × lookback multiplier (see below) |
63-
| `/mpp/dailyActiveTokens` | 1 |
64-
| `/mpp/dailyActivePools` | 1 |
38+
Metered routes are **not** billed at one flat rate. Each catalog entry has a **`credit_weight`** (multiplier on a base GET) or a **stream session** rate for SSE. For the credit plan summary (base unit, Pulse, agents), see [Metering & API Keys → How much each call costs](/agents-and-bds/metering-and-api-keys#how-much-each-call-costs).
39+
40+
The table below is loaded live from [`api/endpoints.json`](https://github.com/powerloom/snapshotter-computes/blob/bds_eth_uniswapv3_core/api/endpoints.json) so it stays aligned when the catalog version or weights change:
41+
42+
<LiveMeteredRoutesTable />
6543

6644
### Time series lookback multiplier
6745

68-
`/mpp/timeSeries/...` is the one route whose cost is **not** captured by the static `credit_weight` alone. On top of its base weight of **5**, the resolver applies a **lookback multiplier** determined by the `time_interval` parameter (how far back the window reaches). Effective debit = **`CREDIT_PER_EPOCH × 5 × multiplier`**. This reflects the number of historical snapshots the resolver must walk to serve the window; for real-time use, per-block `/mpp/token/price/.../{block_number}` is cheaper and more precise.
46+
`/mpp/timeSeries/...` is the one route whose cost is **not** captured by the static `credit_weight` alone. On top of its base weight of **5**, a **lookback multiplier** applies based on `time_interval` (how far back the window reaches). For real-time use, per-block `/mpp/token/price/.../{block_number}` is usually cheaper and more precise.
6947

7048
The tiers are defined in the catalog (`billing_modifier` on the time series route) and load live below:
7149

7250
<LiveTimeseriesMultiplier />
7351

74-
So `/mpp/timeSeries/.../3600/144` (1-hour lookback) costs 5 × 4 = **20×** the base epoch rate; a 24-hour window costs **640×**. The multiplier is keyed off `time_interval` (the lookback seconds), not `step_seconds`.
52+
So `/mpp/timeSeries/.../3600/144` (1-hour lookback) costs **5 × 4 = 20×** a weight-1 GET; a 24-hour window costs **640×** that base. The multiplier is keyed off `time_interval` (the lookback seconds), not `step_seconds`.
7553

7654
## Core consumption patterns
7755

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import React, {useEffect, useMemo, useState} from 'react';
2+
import styles from './styles.module.css';
3+
4+
const DEFAULT_CATALOG_URL =
5+
'https://raw.githubusercontent.com/powerloom/snapshotter-computes/bds_eth_uniswapv3_core/api/endpoints.json';
6+
7+
function epochDenominator(creditPerEpoch) {
8+
if (!Number.isFinite(creditPerEpoch) || creditPerEpoch <= 0) return null;
9+
const den = Math.round(1 / creditPerEpoch);
10+
return Math.abs(den * creditPerEpoch - 1) < 1e-9 ? den : null;
11+
}
12+
13+
function creditsLabel(weight, billing) {
14+
const w = Number.isFinite(weight) && weight > 0 ? weight : 1;
15+
const perEpoch = billing?.credit_per_epoch;
16+
if (!Number.isFinite(perEpoch) || perEpoch <= 0) {
17+
return ${w}`;
18+
}
19+
const den = epochDenominator(perEpoch);
20+
if (den != null) {
21+
return w === 1 ? `1/${den} credit` : `${w}/${den} credits`;
22+
}
23+
const product = w * perEpoch;
24+
return `${product} credit${product === 1 ? '' : 's'}`;
25+
}
26+
27+
function billingColumn(ep, billing) {
28+
const mod = ep.billing_modifier;
29+
if (ep.sse === true || mod?.type === 'stream_session') {
30+
const session =
31+
Number.isFinite(mod?.credits_per_connection) && mod.credits_per_connection > 0
32+
? mod.credits_per_connection
33+
: billing?.credit_per_stream_session;
34+
if (Number.isFinite(session) && session > 0) {
35+
return `${session} credits / session`;
36+
}
37+
return 'flat rate / session';
38+
}
39+
if (mod?.type === 'lookback_multiplier') {
40+
const base = Number.isFinite(ep.credit_weight) ? ep.credit_weight : 5;
41+
return `${creditsLabel(base, billing)} × lookback (see below)`;
42+
}
43+
return creditsLabel(ep.credit_weight, billing);
44+
}
45+
46+
function weightColumn(ep) {
47+
const mod = ep.billing_modifier;
48+
if (ep.sse === true || mod?.type === 'stream_session') {
49+
return '—';
50+
}
51+
if (mod?.type === 'lookback_multiplier') {
52+
const base = Number.isFinite(ep.credit_weight) ? ep.credit_weight : 5;
53+
return `${base} × lookback`;
54+
}
55+
const w = Number.isFinite(ep.credit_weight) ? ep.credit_weight : 1;
56+
return String(w);
57+
}
58+
59+
function meteredEndpoints(catalog) {
60+
const eps = Array.isArray(catalog?.endpoints) ? catalog.endpoints : [];
61+
return eps
62+
.filter((e) => e && e.metered !== false && typeof e.path === 'string')
63+
.sort((a, b) => a.path.localeCompare(b.path));
64+
}
65+
66+
export default function LiveMeteredRoutesTable({catalogUrl = DEFAULT_CATALOG_URL}) {
67+
const [state, setState] = useState({loading: true, error: null, data: null});
68+
69+
useEffect(() => {
70+
const controller = new AbortController();
71+
async function load() {
72+
try {
73+
const response = await fetch(catalogUrl, {
74+
headers: {Accept: 'application/json'},
75+
signal: controller.signal,
76+
});
77+
if (!response.ok) {
78+
throw new Error(`GET catalog returned ${response.status}`);
79+
}
80+
const data = await response.json();
81+
setState({loading: false, error: null, data});
82+
} catch (error) {
83+
if (error.name === 'AbortError') return;
84+
setState({loading: false, error: error.message, data: null});
85+
}
86+
}
87+
load();
88+
return () => controller.abort();
89+
}, [catalogUrl]);
90+
91+
const rows = useMemo(() => meteredEndpoints(state.data), [state.data]);
92+
const billing = state.data?.billing;
93+
const market = state.data?.market;
94+
const version = state.data?.version;
95+
96+
return (
97+
<div className={styles.wrapper}>
98+
<div className={styles.header}>
99+
<div>
100+
<strong>Metered routes (live)</strong>
101+
<p>
102+
Fetched from <code>api/endpoints.json</code> — same catalog the resolver and{' '}
103+
<code>bds-agent</code> use for route matching and credit weights.
104+
</p>
105+
</div>
106+
<a href={catalogUrl} target="_blank" rel="noreferrer">
107+
Open catalog JSON
108+
</a>
109+
</div>
110+
111+
{state.loading && <p className={styles.status}>Loading catalog…</p>}
112+
113+
{state.error && (
114+
<div className={styles.error}>
115+
Could not load the live catalog: <code>{state.error}</code>
116+
<br />
117+
Open the catalog JSON above for the full route list.
118+
</div>
119+
)}
120+
121+
{!state.loading && !state.error && rows.length === 0 && (
122+
<div className={styles.error}>No metered endpoints found in the catalog.</div>
123+
)}
124+
125+
{!state.loading && !state.error && rows.length > 0 && (
126+
<>
127+
<div className={styles.meta}>
128+
{market && <span>{market}</span>}
129+
{Number.isFinite(version) && <span>catalog v{version}</span>}
130+
<span>{rows.length} metered route(s)</span>
131+
{billing?.credit_per_epoch != null && (
132+
<span>base GET ≈ 1/7200 credit</span>
133+
)}
134+
</div>
135+
<div className={styles.tableWrap}>
136+
<table>
137+
<thead>
138+
<tr>
139+
<th>Metered route</th>
140+
<th>
141+
<code>credit_weight</code>
142+
</th>
143+
<th>Typical credits</th>
144+
</tr>
145+
</thead>
146+
<tbody>
147+
{rows.map((ep) => (
148+
<tr key={`${ep.method || 'GET'}:${ep.path}`}>
149+
<td className={styles.pathCell}>
150+
<code>{ep.path}</code>
151+
</td>
152+
<td>{weightColumn(ep)}</td>
153+
<td>{billingColumn(ep, billing)}</td>
154+
</tr>
155+
))}
156+
</tbody>
157+
</table>
158+
</div>
159+
</>
160+
)}
161+
</div>
162+
);
163+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.wrapper {
2+
border: 1px solid var(--ifm-color-emphasis-300);
3+
border-radius: 12px;
4+
padding: 1rem;
5+
margin: 1rem 0 1.5rem;
6+
background: var(--ifm-background-surface-color);
7+
}
8+
9+
.header {
10+
display: flex;
11+
align-items: flex-start;
12+
justify-content: space-between;
13+
gap: 1rem;
14+
margin-bottom: 0.75rem;
15+
}
16+
17+
.header p {
18+
margin: 0.25rem 0 0;
19+
color: var(--ifm-color-emphasis-700);
20+
}
21+
22+
.status,
23+
.error {
24+
margin: 0;
25+
}
26+
27+
.error {
28+
border-left: 3px solid var(--ifm-color-danger);
29+
padding-left: 0.75rem;
30+
color: var(--ifm-color-danger-dark);
31+
}
32+
33+
.meta {
34+
display: flex;
35+
flex-wrap: wrap;
36+
gap: 0.5rem;
37+
margin-bottom: 1rem;
38+
color: var(--ifm-color-emphasis-700);
39+
font-size: 0.9rem;
40+
}
41+
42+
.meta span {
43+
border: 1px solid var(--ifm-color-emphasis-300);
44+
border-radius: 999px;
45+
padding: 0.2rem 0.55rem;
46+
}
47+
48+
.tableWrap {
49+
overflow-x: auto;
50+
}
51+
52+
.tableWrap table {
53+
width: 100%;
54+
margin: 0;
55+
}
56+
57+
.pathCell {
58+
font-size: 0.85rem;
59+
word-break: break-word;
60+
}

0 commit comments

Comments
 (0)