--- name: coingecko-crypto-scraper description: Read CoinGecko market data as structured records via the Apify Actor arman-bd/coingecko-crypto-scraper. Returns one row per coin with price, market cap and rank, fully diluted valuation, 24h volume, 24h high and low, circulating, total and max supply, all-time high and low, and percentage price changes over any of seven windows, all quoted in a currency you choose. Use when a task needs a portfolio snapshot, a market-cap leaderboard, a category screen or a repeating price feed for named assets. Not for historical time series, order books, individual exchange pairs or on-chain data. --- # CoinGecko Scraper Apify Actor `arman-bd/coingecko-crypto-scraper`. One dataset record per coin, holding a point-in-time market snapshot. It takes no credentials of your own. Three ways to drive it: name the coins, name a category, or name neither and take the top of the market by capitalisation. ## When to use it - A portfolio of holdings has to become rows priced in your reporting currency. - You want the top N assets by market cap as a leaderboard or dashboard feed. - You are screening a sector (`layer-1`, `stablecoins`, `meme-token`) on cap, volume and momentum. - You are building your own time series by running a snapshot on a schedule and storing each result. ## When not to use it - Historical prices or OHLC candles. Every record is a snapshot of now; there is no date or range input. - Per-exchange pairs, order books, trades or depth. Use an exchange Actor for that. - On-chain data: holders, transfers, contract calls, TVL. None of it is returned. - Ticker-symbol lookups without translation. The input takes CoinGecko IDs, and they frequently differ from tickers. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/coingecko-crypto-scraper').call({ coinIds: ['bitcoin', 'ethereum', 'solana'], vsCurrency: 'usd', priceChangeWindows: ['1h', '24h', '7d', '30d', '1y'], maxCoins: 250, }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~coingecko-crypto-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"coinIds":["bitcoin","ethereum","solana"],"vsCurrency":"usd"}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/coingecko-crypto-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `coinIds` | string[] | no | `[]` | CoinGecko **IDs**, not tickers: the slug in a coin's CoinGecko URL, so `bitcoin`, `avalanche-2`, `matic-network`. Full coin URLs are accepted and normalised, as is a leading `#`. Case-insensitive. Duplicates are removed. Empty means take the top coins by market cap. | | `vsCurrency` | string | no | `"usd"` | Quote currency for every price, cap and volume figure. Fiat (`usd`, `eur`, `gbp`, `jpy`) or crypto (`btc`, `eth`). Lower-cased before use. An unsupported value makes the request fail and lands in `failures`. Empty string throws. | | `category` | string | no | `""` | One CoinGecko category slug, for example `layer-1`, `stablecoins`, `decentralized-finance-defi`. **Ignored entirely when `coinIds` is non-empty.** An unknown category fails fast for that request. | | `priceChangeWindows` | string[] | no | `["1h","24h","7d","30d","1y"]` | Which percentage-change windows to populate in `priceChanges`. Allowed values: `1h`, `24h`, `7d`, `14d`, `30d`, `200d`, `1y`. `24h` is always added whether you ask for it or not. Anything else is warned about and dropped. | | `maxCoins` | integer | no | `250` | Total cap on saved coins, minimum `1`, maximum `20000`. Coins arrive 250 per request, so the cap sets the request count and therefore the run time. | **The one decision that matters is which of the three modes you are in.** `coinIds` wins over `category`; passing both silently ignores the category. With IDs, the list is truncated to `maxCoins` and then batched 250 at a time, so 12 coins is a single request. Without IDs, the market-cap ranking is paged until the cap is met, so `maxCoins: 1000` is four requests, and requests are spaced about three seconds apart. Set `maxCoins` to what you will actually use. ## Output One record per coin saved. | Field | Type | Notes | |---|---|---| | `id` | string | CoinGecko ID, lower-case. Stable join key across runs. | | `symbol` | string | Ticker, upper-cased. Not unique across the market; do not key on it. | | `name` | string | Display name. | | `vsCurrency` | string | The quote currency every figure below is denominated in. | | `currentPrice` | number \| null | Last price in `vsCurrency`. | | `marketCap` | number \| null | Circulating supply times price. | | `marketCapRank` | number \| null | Global rank by market cap. | | `fullyDilutedValuation` | number \| null | Max supply times price. `null` when max supply is unknown. | | `totalVolume` | number \| null | Traded volume over 24h. | | `high24h` | number \| null | 24h high. | | `low24h` | number \| null | 24h low. | | `priceChange24h` | number \| null | Absolute 24h move in `vsCurrency`. | | `priceChangePercentage24h` | number \| null | Percentage 24h move. | | `priceChanges` | object | One key per requested window, value a percentage or `null`. Keys are exactly the windows that survived validation, always including `24h`. | | `circulatingSupply` | number \| null | Coins in circulation. | | `totalSupply` | number \| null | Issued supply. | | `maxSupply` | number \| null | Hard cap. `null` for uncapped assets. | | `ath` | number \| null | All-time high in `vsCurrency`. | | `athDate` | string \| null | When the all-time high was set, ISO 8601. | | `atl` | number \| null | All-time low in `vsCurrency`. There is no `atlDate`. | | `lastUpdated` | string \| null | The source's own data timestamp, ISO 8601. | | `scrapedAt` | string | When this Actor read it, ISO 8601. | A real record, quoted in USD: ```json { "id": "bitcoin", "symbol": "BTC", "name": "Bitcoin", "vsCurrency": "usd", "currentPrice": 64530, "marketCap": 1294891648024, "marketCapRank": 1, "fullyDilutedValuation": 1294891648024, "totalVolume": 22208254108, "high24h": 64933, "low24h": 63878, "priceChange24h": 38.08, "priceChangePercentage24h": 0.7, "priceChanges": { "24h": 0.7, "1h": -0.1, "7d": 0, "30d": 2.1, "1y": -43.4 }, "circulatingSupply": 20066496, "totalSupply": 20066496, "maxSupply": 21000000, "ath": 126080, "athDate": "2025-10-06T10:57:42.000Z", "atl": 67.81, "lastUpdated": "2026-08-06T11:25:20.000Z", "scrapedAt": "2026-08-06T11:27:19.911Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. **Read it.** It is where a partial run admits that it was partial. ```json { "requestsMade": 1, "requestsFailed": 0, "failures": [], "coinsRequested": 5, "coinsReturnedByApi": 4, "coinsSaved": 4, "unknownCoinIds": ["notarealcoin"], "filters": { "vsCurrency": "usd", "category": null, "priceChangeWindows": ["24h", "1h", "7d", "30d", "1y"], "maxCoins": 10 }, "finishedAt": "2026-08-06T11:27:19.916Z" } ``` Two checks tell you whether the run was complete. `unknownCoinIds` lists every ID you asked for that came back with no data, almost always a ticker used where an ID was needed. `requestsFailed` above zero means whole pages are missing, with one `{ request, error }` entry in `failures` for each. `coinsRequested` is `null` when you did not pass `coinIds`, because in that mode there is no requested count to compare against. Note also that `filters.priceChangeWindows` is the cleaned list actually used, so it is the right thing to read when deciding which keys `priceChanges` carries. ## Behaviour to plan around - **IDs are not tickers.** `AVAX` is `avalanche-2` and `MATIC` is `matic-network`. An ID the source does not recognise is dropped without an error; it appears in `RUN_SUMMARY.unknownCoinIds` and simply has no row. Always reconcile your input list against `id` in the output. - **`coinIds` beats `category`.** If both are set, the category is ignored with no warning in the data. `RUN_SUMMARY.filters.category` still echoes what you passed, so do not read it as proof the category was applied. - **Every numeric field is `null`, never `0`, when there is no value.** An uncapped asset gets `"maxSupply": null` and therefore `"fullyDilutedValuation": null`. Guard arithmetic accordingly instead of treating missing as zero. - **`priceChanges` keys are only the valid windows.** An unsupported window is dropped with a log warning, not an error, so asking for `90d` quietly gives you an object without it. Read the keys, do not assume them. - **`24h` is always present in `priceChanges`,** even if you request only `7d`. It also duplicates `priceChangePercentage24h`. - **This is a snapshot, not a series.** `lastUpdated` is how stale the source's own tick was; `scrapedAt` is how stale your copy is. To build history, schedule the run and keep each result keyed on `id` plus `scrapedAt`. - **`requestsMade` is the planned request count, not the executed one.** The run stops early when the cap is met or a page comes back empty, so with a large `maxCoins` on a small category, `requestsMade` can exceed what was actually issued. Judge completeness from `coinsSaved` and `failures`, not from this number. - **A failed page never aborts the run.** Throttling is retried up to three times with a long backoff; transient server errors get a shorter one. A bad quote currency, an unknown category and a malformed response fail immediately for that request and are recorded. The Actor only throws when every request failed. - **Coins are deduplicated within a run,** so a coin appearing in two overlapping pages is saved once. - **A category is capped by its own size.** Asking for 300 coins in a category holding 200 returns 200 and stops; that is not a failure. ## Recipes **Portfolio snapshot in your reporting currency.** Exactly the assets you hold, run on a schedule. ```json { "coinIds": ["bitcoin", "ethereum", "solana", "chainlink", "uniswap"], "vsCurrency": "eur", "priceChangeWindows": ["24h", "7d", "30d"] } ``` Multiply `currentPrice` by your position size; `priceChanges` gives you the period return columns without further work. **Sector screen.** Pull a whole category, filter downstream. ```json { "category": "layer-1", "vsCurrency": "usd", "priceChangeWindows": ["24h", "7d", "30d"], "maxCoins": 300 } ``` A liquidity screen is one expression: `totalVolume / marketCap > 0.05`. Note that `coinIds` must be absent for the category to apply. **Market leaderboard.** No IDs, no category, just depth. ```json { "vsCurrency": "usd", "maxCoins": 1000, "priceChangeWindows": ["1h", "24h", "7d", "30d", "1y"] } ``` Rows arrive already ordered by `marketCapRank`. Four requests, so budget roughly ten seconds of built-in pacing on top of the fetches. **Cheapest possible repeated poll.** One request, one window, a handful of coins. ```json { "coinIds": ["bitcoin", "ethereum"], "vsCurrency": "usd", "priceChangeWindows": ["24h"], "maxCoins": 2 } ``` Suitable for a frequent schedule. Append each run to a store keyed on `id` and `scrapedAt` to accumulate the history the Actor itself does not provide.