/token-score returns one 0–100 composite describing a Base token’s market structure, computed
from data planes Otto already operates. It is deterministic: no language model is anywhere in the
path, so there is no narrative that can drift away from the numbers.
The formula ships with every answer. Each response carries a formula block containing every
factor’s weight, the exact response field it read, and the full list of breakpoints its curve
interpolates between. Applying those breakpoints to the returned inputs reproduces the returned
sub-scores and the composite exactly. You never have to take the number on trust, and this page is a
description of that block — not a substitute for it.
What it is not
It is also not predictive and not a recommendation. Every factor measures the token’s market as it stands right now. The score does not forecast price, does not rank tokens against each other, andband is a plain label for where the number falls — nothing more.
Why the key is a contract address
/token-score accepts a Base (chain 8453) ERC-20 contract address, 0x followed by 40 hex
characters, and rejects a ticker symbol as malformed — uncharged.
That is a correctness rule, not a limitation. The holder plane is keyed on a bare address on a named
chain, while a symbol resolves through a canonical listing to whichever chain that asset’s main
deployment lives on. Accepting a symbol would let one plane answer about a deployment on one chain
while another answered about a different one, and the composite would fuse two tokens into one score
with nothing in the response saying so. Address-keying makes all three planes provably describe the
same token. For symbol lookups, use /token-price.
The seven factors
Each factor maps a raw input onto a 0–100 sub-score by linear interpolation between published breakpoints, clamped outside the ends. The composite is the weighted mean of the sub-scores that resolved, with the weights renormalised over exactly those factors.
The weights are a judgement, and they are published as one. They are not the output of a
backtest and no claim is made that they predict returns. Liquidity carries the largest single weight
because it is the only factor that bounds whether a position can be exited at all; the two holder
factors together (0.32) outweigh it because distribution is the property a market-structure score
exists to surface. They are returned on every response precisely so you can reweight the sub-scores
yourself.
Two deliberate choices worth knowing
Price direction is not scored. The scored factor is the size of the 24-hour move, because a score that rewarded going up would rank a pump above an established market. The signed change is returned unscored ascontext.priceChange24hPct.
Liquidity is read from the pools, not from the price plane. Otto’s /token-price publishes the
top pool’s reserve, which is the right number for a price lookup and the wrong one here: a token
used mainly as a quote asset reports near nothing at the token level. Measured live on
2026-09-01, Base USDC came back with a top-pool liquidity of 0 while its matched pools held
$26.6M. Summing the matched pools measures what this factor claims to measure. The price plane’s own
figure still serves, unscored, as context.liquidityUsd.
The pooled figure is a floor, not a total: the pool search returns one page, so a token quoted
in hundreds of pools is summed over the pools it returned. context.poolsReturned and
context.matchedPoolCount publish that denominator.
Absent factors are reported, never filled in
When a data plane does not answer, its factors come back withpresent: false, a null sub-score,
and an absenceReason. They are never defaulted to a neutral value and never quietly dropped from
the list, because a buyer could not otherwise tell a factor that was omitted from one that never
existed.
coverage.weightCoverage is the share of the full weighting that actually contributed to the
number, and every present factor carries planeReadAt, the instant its own plane was read.
When the composite is withheld
When the factors that resolved carry less than half the total weighting, the composite is
withheld:
score is null and scoreWithheldReason is insufficient_factor_coverage. Every
sub-score that did resolve is still returned, with its own source and read time.coverage.minWeightCoverage.
The price plane is required: without it the token has no price, no liquidity and no identity, so
the call is refused uncharged rather than served as a partial answer.
Score bands
band is a plain label for where the composite falls. It describes the score, not the token’s
merit.
Two numbers to read carefully
Holder concentration includes contracts. Concentration is measured from the top holders as the holder index reports them, and those addresses routinely include liquidity pools, burn addresses, bridges, staking contracts and treasuries. A perfectly ordinary token can therefore look concentrated. Read the factor as what it is — how much supply sits in a few addresses — not as a claim about insiders. Turnover is returned unscored.context.turnover24h divides 24-hour volume by pooled liquidity.
A very high turnover is consistent with real demand and with wash trading, and this data cannot
tell those apart, so you get the ratio rather than a verdict.
Source and licensing
Every input comes from keyless public DEX data (GeckoTerminal / DexScreener) and a holder index Otto already licenses — the same planes behind/token-price, /pools-search and /holder-analytics.
Nothing here is investment advice.