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.

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  1. Coins List (ID Map) — CoinGecko, read
  2. Coin Data by Token Address — CoinGecko, read
  3. Coin Price by IDs, names or symbols — CoinGecko, read
  4. Cryptocurrency endpoints — ID Map and Metadata — CoinMarketCap, read
  5. Manual — Symbols And Market Ids, Naming Consistency, Unified Networks — CCXT, read
  6. DefiLlama Free API — Coins and Prices — DefiLlama, read
  7. List active symbol mapping for the exchange — CoinAPI, read
  8. Save the Date: MATIC to POL Migration Coming September 4th — Polygon Labs,
  9. 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.