--- name: coinbase-exchange-rates-scraper description: Read Coinbase's public exchange-rate feed through the Apify Actor arman-bd/coinbase-exchange-rates-scraper and get one record per base/quote pair, with the reference rate as a number and optionally the spot, buy and sell quote for the pairs you name. Use when a task needs a conversion table across crypto and fiat, a scheduled price snapshot, treasury FX mid rates with the trading spread, or a checkout pricing table for one base currency. Not for order books, OHLC candles, historical time series or trade execution. --- # Coinbase Rates Scraper: Crypto & Fiat Exchange Rates Apify Actor `arman-bd/coinbase-exchange-rates-scraper`. Name one or more base currencies, crypto or fiat, and get their rate against every currency Coinbase publishes, roughly 640 quotes per base. Naming a short quote list additionally enables the spot, buy and sell quote per pair. It takes no credentials. ## When to use it - Building a full conversion table for a product's checkout, one base against everything. - A scheduled snapshot of a handful of pairs, diffed over time into a price series you own. - Treasury or invoicing work that needs the mid rate and the buy/sell spread side by side. - Cross-rate sanity checks, because fiat-to-fiat and crypto-to-fiat come back from the same call in the same shape. ## When not to use it - Order books, depth, candles or any OHLC history. Each run is a single instant. - Historical rates. There is no date input. Backfilling means scheduling the Actor and keeping your own rows. - Trading. Nothing here places, quotes or reserves an order. - Currencies Coinbase does not publish. A quote it does not carry is silently absent, not estimated. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/coinbase-exchange-rates-scraper').call({ baseCurrencies: ['BTC', 'ETH', 'EUR'], quoteCurrencies: ['USD', 'EUR', 'GBP'], includeSpotPrices: true, }); 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~coinbase-exchange-rates-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"baseCurrencies":["BTC"],"quoteCurrencies":["USD","EUR"],"includeSpotPrices":true}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/coinbase-exchange-rates-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `baseCurrencies` | string[] | no | `["BTC"]` | Codes to price *from*, crypto or fiat. One request each. Case and separators are normalised, so `btc`, `BTC` and `b-t-c` are the same entry, and duplicates are dropped. An empty list falls back to `BTC`. | | `quoteCurrencies` | string[] | no | `[]` | Keep only these quotes. Empty saves every quote Coinbase publishes for that base, around 636 rows. Same normalisation as the bases. | | `includeSpotPrices` | boolean | no | `true` | Adds `spotPrice`, `buyPrice` and `sellPrice`. Honoured only when `quoteCurrencies` is non-empty **and** bases times quotes is 60 or fewer. Outside that it is skipped, the rates are still saved, and `RUN_SUMMARY.spotPricesIncluded` reports `false`. | **`quoteCurrencies` is the switch that decides everything else.** Left empty you get a complete table, hundreds of rows per base, from a single request per base, and spot prices are off no matter what you asked for. Filled in with a few codes you get a small dashboard-sized dataset and, inside the 60-pair budget, the tradable prices as well at a cost of three extra requests per pair. There is no middle setting: `includeSpotPrices` on its own does nothing. ## Output One record per base/quote pair that Coinbase actually publishes. | Field | Type | Notes | |---|---|---| | `baseCurrency` | string | The currency being priced, as Coinbase echoes it back rather than as you typed it. | | `quoteCurrency` | string | The currency it is priced in. | | `rate` | number \| null | Units of `quoteCurrency` per one unit of `baseCurrency`, from the reference rate table. Converted to a number, not the string the source sends. | | `spotPrice` | number \| null | Trading spot price for the pair. `null` when price lookups were skipped or the pair is not tradable. | | `buyPrice` | number \| null | Buy quote, spread included. Same null conditions. | | `sellPrice` | number \| null | Sell quote, spread included. Same null conditions. | | `timestamp` | string | ISO 8601. When the rate snapshot for this **base** was taken, so every row from one base shares it. | | `scrapedAt` | string | ISO 8601. When this row was written. Drifts ahead of `timestamp` when spot lookups are on. | A real record, spot prices enabled: ```json { "baseCurrency": "BTC", "quoteCurrency": "USD", "rate": 64515.83, "spotPrice": 64506.665, "buyPrice": 64508.545, "sellPrice": 64508.545, "timestamp": "2026-08-06T11:30:07.310Z", "scrapedAt": "2026-08-06T11:30:07.862Z" } ``` ## 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 { "basesRequested": 4, "basesSaved": 3, "basesFailed": 1, "failures": [ { "baseCurrency": "NOTREAL", "error": "rejected: base currency not recognized" } ], "ratesSaved": 9, "spotPricesIncluded": true, "unsupportedQuotes": [ { "baseCurrency": "BTC", "quoteCurrency": "ZZZ" } ], "filters": { "baseCurrencies": ["BTC", "ETH", "EUR", "NOTREAL"], "quoteCurrencies": ["USD", "EUR", "GBP", "ZZZ"], "includeSpotPrices": true }, "finishedAt": "2026-08-06T11:30:10.318Z" } ``` Three fields settle almost every question. `basesFailed` above zero means a base currency was rejected outright and contributes no rows at all, with the reason in `failures`. `unsupportedQuotes` lists the base/quote combinations Coinbase does not publish: those pairs are **missing from the dataset entirely**, so a join against your requested quote list will have holes unless you check here. `spotPricesIncluded: false` alongside `filters.includeSpotPrices: true` is the signal that the pair budget was exceeded and every price column is null by design, not by outage. ## Behaviour to plan around - **`includeSpotPrices` alone does nothing.** With an empty `quoteCurrencies` it is always skipped, because one base expands to hundreds of pairs. Name the quotes. - **The 60-pair budget counts bases times quotes.** Four bases and twenty quotes is 80 pairs, over the line, and the whole run loses its prices. Trim either list. - **A missing quote produces no row.** It is recorded in `RUN_SUMMARY.unsupportedQuotes` and nothing is written to the dataset. Do not read absence as a zero rate. - **A non-tradable pair still produces a row.** `rate` is filled, the three price fields are `null`. That is the opposite of the previous case and the two are easy to confuse. - **`rate` and `spotPrice` are different numbers by design.** `rate` comes from the reference table, `spotPrice` from the trading books. They track closely and will not match exactly. Only `spotPrice` has a matching buy and sell. - **`baseCurrency` is echoed back, not your input.** It is normalised upstream, so key your own tables on the returned value or on the pair. - **A failing base never aborts the run.** It lands in `RUN_SUMMARY.failures` and the next base proceeds. The Actor throws only when every base failed. - **Wrong codes are not retried.** Rejections and not-found responses fail fast; only transient failures get three attempts with linear backoff. - **`timestamp` is per base, not per row.** Rows from one base share one instant even though `scrapedAt` walks forward. Use `timestamp` when aligning a snapshot. ## Recipes **Full conversion table for a product.** One base, every quote, one request. Refresh on a schedule. ```json { "baseCurrencies": ["USD"], "quoteCurrencies": [], "includeSpotPrices": false } ``` Index the rows by `quoteCurrency` and you have a complete lookup for checkout. **Treasury FX with the spread.** Fifteen pairs, comfortably inside the budget, so the buy and sell columns are populated. ```json { "baseCurrencies": ["USD", "EUR", "GBP"], "quoteCurrencies": ["USD", "EUR", "GBP", "JPY", "CHF"], "includeSpotPrices": true } ``` Book at `rate`, reconcile against `buyPrice` and `sellPrice` for the cost of the trade. **Crypto price ticker.** Three coins against two fiats, six pairs, tradable prices on. ```json { "baseCurrencies": ["BTC", "ETH", "SOL"], "quoteCurrencies": ["USD", "EUR"], "includeSpotPrices": true } ``` Display `spotPrice` and keep `timestamp` next to it so a stale run is visible. **Build your own history.** Schedule the smallest useful run and append every dataset to one table. ```json { "baseCurrencies": ["BTC"], "quoteCurrencies": ["USD"], "includeSpotPrices": true } ``` Key each row on `baseCurrency`, `quoteCurrency` and `timestamp`. The Actor has no historical mode, so this is the only way to get a series.