How to map a ticker to exactly one coin
A ticker names a dozen tokens. Resolving one to a single asset across CoinGecko, CoinMarketCap, CCXT and the chains, and what a rename does to the table.
Never key on the ticker. Resolve it once to a vendor ID, a CoinGecko string or a CoinMarketCap number, and store the chain and contract address beside it, because that pair is the only identifier two vendors will agree on. Exchange pair names map through CCXT's market table. Then re-check the table on a schedule: tickers are shared, renamed and bridged, and a mapping built once goes wrong without raising an error.
The short way
Resolve the ticker once, against a vendor's ID map, and never send it again. Both of the large aggregators publish one, and both answer without a key.
GET https://api.coingecko.com/api/v3/coins/list?include_platform=true
GET https://pro-api.coinmarketcap.com/public-api/v1/cryptocurrency/map?symbol=POL,MATIC
The first returns every active coin CoinGecko tracks, in one unpaginated response, as a string
id, a symbol, a name, and with include_platform a map of every chain the token lives on to
its contract address there. The second returns CoinMarketCap's numeric id for every coin using
each symbol, with a platform object and an is_active flag. Build two indexes from the first,
one from ticker to candidate IDs and one from chain and address to exactly one ID:
import json
import urllib.request
URL = "https://api.coingecko.com/api/v3/coins/list?include_platform=true"
with urllib.request.urlopen(URL, timeout=60) as response:
coins = json.load(response)
by_symbol, by_contract = {}, {}
for coin in coins:
by_symbol.setdefault(coin["symbol"].lower(), []).append(coin["id"])
for chain, address in (coin.get("platforms") or {}).items():
if not chain or not address:
continue # a few entries arrive with an empty chain or address
if address.startswith("0x"):
address = address.lower() # hex: the case carries no identity
by_contract[(chain, address)] = coin["id"]
print(by_symbol["pol"])
print(by_contract[("ethereum", "0x455e53cbb86018ac2b8092fdcd39d8444affc3f6")])
Run on 26 September 2026, the first line printed four IDs and the second printed one:
polygon-ecosystem-token. That asymmetry is the whole page. A ticker is a search term with
several answers; a chain and a contract address is a key with one. Across the 21,602 entries
that call returned, 2,505 tickers were shared by more than one coin, and no chain-and-address
pair was shared by two.
So store three things per asset: the vendor ID you price from, the chain, and the address. The ticker goes in a display column and nowhere else.
What the options are
The two identifier layers. The CoinGecko API keys on strings —
bitcoin, polygon-ecosystem-token — and its coin list is the cheapest complete dump in the
market: one call, no pagination, refreshed every 30 minutes on the keyless and Demo tiers and
every five on paid ones. Going the other way, /coins/{platform}/contract/{address} returns the
coin for a chain and an address, where the platform is one of the IDs from /asset_platforms.
The CoinMarketCap API keys on integers, and its documentation
recommends the CMC ID over the symbol "to securely identify cryptocurrencies with our other
endpoints and in your own application logic". Its map takes listing_status=inactive and
untracked as well as the default active, and /v2/cryptocurrency/info accepts an address
parameter, so a contract resolves to an ID there too. Both run on the keyless public API.
A price lookup that takes the address itself. DefiLlama's coins API is
keyed by {chain}:{address}, or by coingecko:{id} for assets with no contract — so a table
that already holds chain and address needs no vendor ID at all to be priced. Its /v2/chains
endpoint is the other useful half: per chain it returns the EVM chain ID and, where the chain has
a token of its own, that token's CoinGecko and CoinMarketCap IDs, which is a ready-made join
between the three schemes for the one asset per chain that has no contract.
The venue's own pair names. Exchanges do not use tickers either; they use market IDs. The
CCXT manual lists what BTC/USD is called on various exchanges — btcusd,
XBTUSD, tBTCUSD, XXBTZUSD, and 42 — and load_markets() builds the table from each
venue's live listing, keyed both ways: exchange.markets[symbol]["id"] and
exchange.markets_by_id[id][0]. Unified symbols are BASE/QUOTE for spot, BASE/QUOTE:SETTLE
for a perpetual; futures add an expiry, and options an expiry, a strike and put or call. The currency
renames it applies are in exchange.commonCurrencies, which you can override before the markets
load. How to place an order on several exchanges
is the same table from the trading side.
One scheme, bought. CoinAPI names every market
EXCHANGE_TYPE_BASE_QUOTE — BINANCE_SPOT_BTC_USDT — and /v1/symbols/map/{exchange_id}
returns, per venue, its own symbol_id beside the exchange's symbol_id_exchange, and its
asset_id_base beside the exchange's asset_id_base_exchange. Asset records carry a
chain_addresses list. It is the only option here with no free way in: the entry point is a
metered account with a card on file.
Where this breaks
Every lookup that returns one answer made a choice. Ask for a symbol and get a single
record, and somebody ranked the candidates for you. CoinMarketCap's v1 metadata endpoint returns
"the highest ranked coin using that symbol"; v2 returns an array, "due to the fact that a symbol
is not unique". CoinGecko's price endpoint accepts symbols and, with include_tokens=top,
returns "top-ranked tokens by market cap or volume"; all returns every match instead. CCXT
settles a clash between coins that share a code on different exchanges by market capitalisation, so
HOT/USD is Holo and the other token trades as Hydro Protocol/USD. Each rule is reasonable and
each is a ranking — which means the answer to "which BTC" can change without any of the coins
changing, if another one climbs past it.
The same token on six chains is six addresses, and the vendors disagree about which of them
are the same asset. CoinMarketCap's USDC is one ID, 3408, whose metadata lists 95 contract
addresses. CoinGecko's usd-coin lists 36 platforms, and 62 IDs in the same list carry the
ticker usdc — most of them bridged copies with IDs of their own. Neither is wrong: a token
issued on a chain and a token bridged onto it are different claims on the same dollar, and each
vendor draws the line where its methodology does.
How to reconcile a token's supply and market cap is
what that line does to a supply figure. For a mapping, it means a CoinGecko ID and a
CoinMarketCap ID do not always name the same set of contracts, and a join between the two
through the ticker will pair them wrongly.
The address alone is not a key either. The same string can be deployed on several chains. In
the CoinGecko list, 1,808 addresses appear under more than one platform, and wrapped-bitcoin
lists 0x0555e30da8f98308edb960aa94c0db47230d2b9c on Base, Sonic and Berachain alike. The key is
the pair, and the chain half has no shared spelling: Polygon PoS is EVM chain 137,
polygon-pos on CoinGecko, polygon_pos on GeckoTerminal, polygon in DefiLlama's coin keys,
POLYGON in CoinAPI's chain IDs and MATIC in the CCXT manual's table of unified networks.
Store the EVM chain ID where there is one — CoinGecko's /asset_platforms and DefiLlama's
/v2/chains both return it — and map names to it, not to each other.
GeckoTerminal does part of this for you by returning a
coingecko_asset_platform_id on every network.
A rename is a new ID, and the old one does not forward. Polygon replaced MATIC with POL as the
native token of Polygon PoS on 4 September 2024. On 26 September 2026, CoinGecko still listed
matic-network as an active entry named "MATIC (migrated to POL)", with no contracts, beside
polygon-ecosystem-token, "POL (ex-MATIC)". CoinMarketCap's MATIC, ID 3890, was inactive; its
POL is ID 28321, with history from 25 October 2023. A series keyed to the old ID ends at the
rename rather than continuing under the new ticker, and a job that treats an empty response as
"no trades today" will not notice. Labels lag further than IDs: DefiLlama's price for
polygon:0x0000000000000000000000000000000000001010 is POL's price under the symbol matic.
Delisted is not deleted, and the free tier cannot see it. CoinGecko's coin list returns
inactive coins only with status=inactive, which needs the Analyst plan or above; a stored ID for
a coin that stopped trading simply drops out of the free list. CoinMarketCap's map returns
inactive coins on the keyless tier, and asking it for a symbol returns them unasked — the
symbol=POL,MATIC request above came back with five records, three of them inactive. Keep
inactive IDs in your table and flag them; deleting them is how a backtest loses the coins that
went to zero.
Checksummed and lowered addresses are the same address. ERC-55 puts a checksum in the
capitalisation of hex letters, and 211 EVM addresses in CoinGecko's list arrive in mixed case
while CoinMarketCap returns USDC on zkSync Era as 0x1d17CBcF…. Compare hex addresses lower-cased.
Do not lower-case everything: a Solana mint address is base58, where upper and lower case are
different characters.
Wrapped is a different asset with a different risk. WBTC is not BTC, and both aggregators give it
its own ID — wrapped-bitcoin beside bitcoin on CoinGecko, where Bitcoin has no contract at
all, and 3717 beside 1 on CoinMarketCap. The price
tracks until the custodian or the bridge behind it fails, which is exactly when the difference
matters. Map the ticker a user typed to the asset they meant, and keep the wrapper as its own row.
If you outgrow this
If the problem is that the table drifts, rebuild it on a schedule rather than patching it: pull the full list from the vendor you price from, diff it against yesterday's, and review what appeared, disappeared or changed symbol before anything downstream reads it. The CoinGecko list is one call and CoinMarketCap's map is keyless, so the cost of doing this daily is a few requests.
If the problem is exchange coverage — the same asset across many order books rather than one aggregate — CCXT's market tables are free and rebuilt every time they load, and CoinAPI sells the same mapping maintained for you across its venues, with the archive behind it. Market data APIs is the rest of that shelf.
If the problem is a migration off an aggregator that did the mapping for you, that is most of
the work, and the CryptoCompare page says so in its migration notes:
its fsym was a ticker, and every replacement wants an ID.
And if the problem is a token no aggregator lists, there is no vendor ID to resolve to. Key it by chain and address from the start, price it from the pool it trades in, and read how to get the price of a DEX pair before trusting that number.
The tools named above
In the order this page puts them in, which is an editorial judgement and not a ranking anyone paid for.
CoinGecko API
One keyless call returns every coin ID with its contract on every chain, and a second resolves a chain and address back to the ID.
Prices, market data and onchain DEX data for 18,000+ coins, via REST, websocket or MCP.
$35/moFree tier
CoinMarketCap API
The numeric ID map, keyless, with inactive coins on request; the metadata endpoint takes a contract address and returns the ID it belongs to.
Prices, rankings, DEX and derivatives data, metered by rows returned rather than by call.
$35/moFree tier
DefiLlama
Prices keyed by chain and address, or by CoinGecko ID, and a chains table that joins CoinGecko, CoinMarketCap and EVM chain IDs.
DeFi's default metric set, with the TVL definition written down in public.
$49/moFree tier
CCXT
Loads each venue's market list and maps its own pair strings to one unified symbol, with the currency renames overridable in code.
One MIT client for 104 crypto exchanges, 76 of them over websocket.
FreeFree tierOpen source
CoinAPI Market Data API
One paid symbol scheme over its venues, with a per-exchange map back to the venue's own codes and chain addresses on each asset.
One symbol scheme over 400+ venues, metered in REST credits and WebSocket gigabytes.
$79/mo
FAQ
Why does a ticker return more than one coin?
Because nobody allocates tickers. Any token contract can call itself BTC, and bridged copies of a real asset usually keep the original's ticker on purpose. On 26 September 2026 CoinGecko's coin list held 21,602 entries, and 2,505 tickers were shared by more than one of them; btc alone belonged to 12 IDs. CoinMarketCap's own metadata documentation says it outright — a symbol is not unique.
Should I store the CoinGecko ID or the CoinMarketCap ID?
Whichever vendor you actually pull prices from, plus the chain and contract address for any token that has one. The vendor ID is what the vendor's endpoints accept; the chain and address pair is what lets you move to another vendor without re-resolving every row by hand. DefiLlama's chains table already carries both vendors' IDs for each chain's own token, which is a useful check on your own mapping.
What happened to MATIC?
Polygon replaced it with POL as the native gas and staking token of Polygon PoS on 4 September 2024, automatically on that chain and through a one-to-one migration contract for MATIC held on Ethereum. The data vendors did not rename the old record. They gave POL a new ID and left MATIC behind, so a price series keyed to the old ID stops rather than continuing under the new ticker.
Is an Ethereum contract address case-sensitive?
No, but it is printed as if it were. ERC-55 encodes a checksum in the capitalisation of the hex letters, so the same address arrives in lower case from one vendor and mixed case from another. Lower-case hex addresses before comparing them, and only hex ones — a Solana address is base58, where the case is part of the value.
Sources
- Coins List (ID Map) — CoinGecko, read
- Coin Data by Token Address — CoinGecko, read
- Coin Price by IDs, names or symbols — CoinGecko, read
- Cryptocurrency endpoints — ID Map and Metadata — CoinMarketCap, read
- Manual — Symbols And Market Ids, Naming Consistency, Unified Networks — CCXT, read
- DefiLlama Free API — Coins and Prices — DefiLlama, read
- List active symbol mapping for the exchange — CoinAPI, read
- Save the Date: MATIC to POL Migration Coming September 4th — Polygon Labs,
- ERC-55: Mixed-case checksum address encoding — Ethereum Improvement Proposals, . Final status, and it is still the form in which wallets and block explorers print Ethereum addresses.
The catalogue next door
This page names a handful of products. The rest of them are in Crypto Market Data APIs, each filled in against the same schema, with the fields to narrow it yourself.
Last updated . Corrected in place — an endpoint that moves is a bug on this page, not a new post.