# AlgoTerminal > Standardized financial KPIs for Algorand DeFi protocols, priced per query in > USDC over x402. Built for autonomous agents and trading bots. One consistent accounting policy across every protocol we cover, so a DEX and a lending market are directly comparable. Data is computed by us from public on-chain and public-API sources. No API key, no signup, no subscription — the payment is the authentication. ## Payment - Protocol: x402 v2, scheme `exact` - Network: Algorand MainNet (`algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=`) - Asset: USDC, ASA 31566704 (6 decimals) - Facilitator: https://facilitator.goplausible.xyz - payTo: 36AZ3YZGHLUFFVU4STAQGSQMRB2W3TK5VVTKZVN6L2LH7NRCK7X27TMEJM - Network fee sponsored by the facilitator — you need USDC only, no ALGO. - Payment is settled only after a successful response. Errors are never charged. - We finish within `maxTimeoutSeconds: 30`, or we return 504 and charge nothing. ### Your payment's validity window is the real clock Settlement happens *after* we produce your answer, so the deadline that matters is `lastValid` on the payment transaction **you** signed — not our timeout. An x402 AVM client builds that group through algokit, whose default validity window is 10 rounds: about 31 seconds at ~2.75 s/round. We read it off your payment and hold the request open only for as long as it is still settleable, minus the settle round-trip. If we run out of that window we return **504 `PAYMENT_WINDOW_EXPIRED`**. Nothing is settled and nothing is charged — you keep your USDC and we keep the data. That is deliberate: serving you data we can no longer collect on would be a free tier we never decided to offer. What this means in practice: - Cached reads finish in milliseconds. The window is never a factor. - `?fresh=true` forces an upstream fetch and is the one path that can approach it. It is also the path where a 504 costs you nothing, so retrying is safe. - Want more room? Build your payment with a longer validity window. We will use what you give us, up to 30 seconds. ## Free endpoints (no payment) - `GET /health` — Liveness; must never require payment - `GET /catalog` — Capability discovery; an agent must be able to learn what we sell before buying - `GET /openapi.json` — Machine-readable spec - `GET /llms.txt` — Agent discovery (llmstxt.org) - `GET /methodology` — Published accounting policy (DATA_SCHEMA.md) - `GET /schema/kpi-fact.json` — The KpiFact envelope as JSON Schema — generate types, validate a response Free evaluation, end to end: https://api-testnet-production-a3ec.up.railway.app The same API on Algorand TestNet, where the paid routes settle in TestNet USDC (ASA 10458941) from the free dispenser. Every route above behaves identically, including the 402s and the settlement, so you can prove the integration and judge the data before you spend a real cent. We would rather you did. ## Paid endpoints - `GET /metric/{protocol}/{kpi}` - $0.005 — Cache-backed lookup - $0.02 — ?fresh=true — forces an upstream round-trip; we sell recency honestly - $0.03 — kpi=active_users_24h — indexer aggregation is materially more expensive - `GET /compare` - $0.05 — Flat, 2-5 protocols - $0.08 — ?fresh=true — forces an upstream round-trip on EVERY leg - `POST /ask` _(not yet live)_ - $0.15 — Standard synthesis - $0.2 — ?depth=deep — wider KPI sweep + longer synthesis budget ## What you are buying when our cache is cold A labelled stale answer is charged for; a non-answer is not. ?fresh=true is not charged unless the number returned is fresh. Every response says which of these you got, in the body and in a header: - `cache: "hit"`, `stale: false` — computed within this KPI's TTL. - `cache: "miss"`, `stale: false` — computed for you, just now. - `cache: "stale"`, `stale: true` — a previously computed number served past its TTL, or the last-known-good snapshot, while a refresh runs. `confidence` carries the penalty for it and `notes` says how old it is and why. Charged: a labelled stale answer. It is a real number, correctly described. Not charged: any 4xx or 5xx, including `UPSTREAM_UNAVAILABLE` when every tier is exhausted, and any answer whose confidence has fallen to the 0.40 floor — at that point we can no longer bound the error, so we decline instead of selling it. Not charged: `?fresh=true` that could not produce a fresh number. Recency is what that tier is for; if we cannot deliver it, the request was not fulfilled. ## Response shape Every `/metric` response is one `KpiFact`: ```json { "metric": "tvl", "protocol": "tinyman", "value": 5344337.0, "unit": "USD", "timestamp": "2026-09-08T14:32:11Z", "as_of": "2026-09-08T14:30:00Z", "source": [{ "name": "tinyman-analytics", "url": "...", "kind": "rest", "retrieved_at": "2026-09-08T14:30:02Z" }], "confidence": 0.70, "is_estimated": false, "estimation_method": null, "methodology_version": "1.2.0", "cache": "hit", "stale": false, "coverage": { "entities": 412, "excluded": 7, "basis": "all_pools_usd_priced" }, "notes": ["..."] } ``` `timestamp` is when we computed; `as_of` is the moment the data describes. They are never the same field. **The envelope has a machine-readable contract.** `https://api-production-36692.up.railway.app/schema/kpi-fact.json` is that object as JSON Schema (draft 2020-12), free and stamped `x-methodology-version: "1.2.0"`. Generate types from it in your language, or validate what you received before you act on it — including the rules that are not obvious from the example above: a `value` may be `null` only alongside an `error`, an `is_estimated: true` fact always carries a non-empty `estimation_method`, and a RATIO is a decimal fraction rather than a percentage. It is a contract, not a client library: we publish what a response IS, and leave fetching it to your stock x402 client. See `https://api-production-36692.up.railway.app/methodology` §7 for what a version bump is allowed to change. Those are real values from this route, not illustrative ones. Note the 0.70: `tvl` is denominated in USD, so it is capped by the price confidence of the assets in the pools, and `notes` says exactly which step cost what. A KPI read straight off the chain scores higher. ## Comparing protocols, and when a comparison is not one `GET /compare?protocols=a,b,c&metric=x` returns the same KPI across 2-5 protocols with the ranking, the spread and a generated statement of what makes them comparable. It is composed from the same cached facts `/metric` serves, is never cached as a unit, and reports the worst cache state across its legs. - **Every leg appears in `facts[]`**, including the ones that failed, as an error fact with a reason. One bad protocol never fails the whole call. - **2 or more legs resolve** -> `200` with `partial: true` and `excluded_protocols`. Charged: you got a usable comparison. - **Fewer than 2 resolve** -> `502 INSUFFICIENT_DATA`. **Not charged.** A one-way "comparison" is not the product, and charging for a non-answer is the thing that stops an agent calling twice. - **The metric applies to none of the protocols you named** -> `422 KPI_NOT_APPLICABLE_TO_ANY`. **Not charged.** Nothing was computed and nothing could have been. - `comparability.confidence` is the **MINIMUM** across legs, never the mean. A comparison is only as trustworthy as its weakest side. - `ranking` is strictly descending by value for every KPI, and says so in `ranking_basis`. Rank 1 is the largest number, not the "best" one — some of these are arguably better low, and which is better depends on which side of the trade you are on. We do not take that position for you. - `spread.ratio` is `null`, never `Infinity` or `NaN`, whenever the lowest value is zero or negative and the quotient would not be a real multiple. `spread.max` and `spread.min` are always the measured values. - `comparability.caveats` is generated per response and is empty when nothing warrants one. When it is not empty it is the part worth reading: it names legs measured on a different `coverage.basis`, legs that are estimates, legs below the 0.7 confidence line, and — when the legs span protocol types — why a DEX and a lending market belong on this axis at all. ## Asking a question instead of naming a metric `POST /ask` takes a natural-language question and answers it strictly from the same facts `/metric` sells. It is descriptive only. ```json { "question": "Which Algorand DeFi protocol generates the most fee revenue per dollar of TVL?", "format": "both", "max_facts": 12 } ``` - **`facts[]` is always returned**, including under `format: "prose"`. Every number in the prose corresponds to a fact in that array, and that is enforced by a check on our side, not just requested in a prompt: an answer containing a number we cannot point at is never returned. The prose is a convenience over the data, never a substitute for it — read the numbers and ignore the narrative if you prefer. - **`citations[]`** maps each claim to the index of the fact it rests on. - **No forecasts, no price targets, no trading advice.** A question asking for one returns `422 OUT_OF_SCOPE`. **Not charged.** - **A question we cannot map onto our coverage** returns `422 UNROUTABLE_QUESTION` with the list of protocols and KPIs we do publish. **Not charged.** Probe freely: the routing step is cheap and we eat it rather than charge for a non-answer. - **Routed but nothing resolved, or an answer that failed our own grounding checks**, returns `502 INSUFFICIENT_DATA`. **Not charged.** - Any fact below the 0.7 confidence line is explicitly caveated in the prose and in `caveats[]`. - A KPI a protocol declines to publish is stated, never omitted. Ask "which protocol has the highest take rate" and the answer says that Pact does not publish its fee split — it does not quietly rank the other two. - `?depth=deep` is a query parameter, not a body field, because it is priced and the price is quoted before the body is read. ## Coverage - **Folks Finance** (`folks`, lending) — 12 KPIs: tvl, total_borrows, utilization, supply_apr, borrow_apr, gross_fees_24h, supply_side_revenue_24h, protocol_revenue_24h, take_rate, capital_efficiency, active_users_24h, pool_count - **Pact** (`pact`, dex) — 6 KPIs: tvl, volume_24h, gross_fees_24h, capital_efficiency, volume_to_tvl, pool_count - _Declines_ (404 `KPI_NOT_APPLICABLE` with the reason, never charged): protocol_revenue_24h, supply_side_revenue_24h, take_rate, fee_apr, active_users_24h - **Tinyman** (`tinyman`, dex) — 11 KPIs: tvl, volume_24h, gross_fees_24h, supply_side_revenue_24h, protocol_revenue_24h, take_rate, capital_efficiency, fee_apr, volume_to_tvl, active_users_24h, pool_count A declined KPI is not a gap in our coverage — it is a fact about the source, and we return it as one. `GET /metric/pact/take_rate` answers 404 `KPI_NOT_APPLICABLE` with a paragraph explaining that Pact's own `pact_fee_bps` is null on all 3,961 of its pools, so the only honest bound on its protocol revenue is "at least $0". We could publish $0.00 and rank Pact last; that would be a statement about disclosure wearing the clothes of a statement about economics. Every decline is readable for free at `/catalog`, under `protocols[].declined`, before you spend anything. Live capability list, generated from the connectors themselves: https://api-production-36692.up.railway.app/catalog ## Example ```bash # 1. Learn the price (free) curl -s https://api-production-36692.up.railway.app/catalog | jq '.routes' # 2. Ask without paying — the 402 tells you what it costs, in a header and a body curl -si https://api-production-36692.up.railway.app/metric/tinyman/tvl # 3. Pay and read, with any x402 client # npm i @x402/fetch @x402/avm ``` ## What we do not do - We do not return a plausible-looking zero. A number we cannot compute is `null` with a reason, or an error — never a 0 that looks like an answer. - We do not serve a stale number as a fresh one. Freshness is in the response, not only in our logs. - We do not change a formula silently. A formula change is a `methodology_version` bump, published at https://api-production-36692.up.railway.app/methodology.