Magic Eden Collections
Solana NFT collections on Magic Eden — floor price, market cap, supply, owners, volume, sales, offers and listings.
312 rows in the dataset
Download the dataset
Get every row this scraper has collected as one CSV or JSONL file. If the data is older than maxAgeHours, the scraper is re-run with its configured settings first. The key needs the datasets:export scope.
DOWNLOAD_ID=$(curl -s -X POST "https://api.pandorlabs.com/v1/datasets/magiceden-collections/downloads" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -H "Content-Type: application/json" \ -d '{"maxAgeHours":24,"format":"csv"}' | jq -r '.id')while true; do STATUS=$(curl -s "https://api.pandorlabs.com/v1/downloads/$DOWNLOAD_ID" \ -H "Authorization: Bearer $PANDORLABS_KEY" | jq -r '.status') [ "$STATUS" != "refreshing" ] && break sleep 5donecurl -s "https://api.pandorlabs.com/v1/downloads/$DOWNLOAD_ID/file" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -o dataset.csvAt its current 312 rows, this download costs 4 credits when the data is fresh enough, or 8 credits when it has to be refreshed first.
The rate is 1 credit per 100 rows cached and 2 credits live, charged on the rows in the dataset when the download becomes ready. A failed refresh is not charged. The file stays downloadable for 24 hours. See pricing.
Start a run
To choose the targets yourself, send the parameters below as input. The call returns 202 with the run; the key needs the scrapers:run scope and the account a positive credit balance.
curl -X POST "https://api.pandorlabs.com/v1/scrapers/magiceden-collections/runs" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "source": "auto", "collectionSymbols": [ "okay_bears" ], "maxCollections": 500, "window": "30d" } }'A run is billed at the live rate once it finishes: 2 credits per 100 rows it collected. A run that collects nothing costs nothing.
Parameters
Every parameter is optional. Anything you leave out uses the default shown here; an unknown name or an invalid value is rejected with a 400 that lists each problem.
Targeting
sourceenum
Dataset
The ranked leaderboard carries market cap, total supply, owner count and a real ordering, but only for the ~3,900 collections it tracks. The catalog covers every collection Magic Eden has indexed, in an undocumented order, with no money columns at all.
- Default
auto- Allowed values
autorankedcatalog
collectionSymbolsstring[]
Collection symbols
Leave empty to discover collections from the chosen dataset. Naming symbols skips discovery entirely and refreshes exactly those rows.
- Default
- none
- Example
okay_bears
maxCollectionsnumber
Max collections per run
Ceiling on discovered collections. The ranked universe is about 3,900 at the 30d window and about 370 at 1d; the catalog reaches roughly 30,000 before it refuses deeper offsets. A ceiling that truncates always warns.
- Default
500- Range
- 1 to 10000
windowenum
Leaderboard window
Scopes the volume and sales columns on the ranked path, and also how many collections that path returns — 30d covers ~3,900, 1d only ~370. Ignored on the catalog path.
- Default
30d- Allowed values
10m1h6h1d7d30d
sortenum
Leaderboard ranking
Which column the leaderboard ranks by, and therefore what `market_cap_rank` on the row is a rank of. Ranked path only.
- Default
marketCap- Allowed values
marketCapvolumevolumePercentageChangesalessalesPercentageChangetotalSalesfloorPricefloorPricePercentageChangetopOfferlistedOverSupplyownerPercentagependingownerCountlistedCountmintedmintedVolume
verifiedOnlyboolean
Verified collections only
Sends the marketplace’s own quality filter on the ranked path. Off by default because it barely moves the result — 370 collections became 367 when it was measured — while `is_verified` is recorded on every row anyway.
- Default
false
Enrichment
collectStatsboolean
Fetch per-collection stats
One extra request per collection for 24h/7d/30d volume, sale counts, average prices, the best standing offer and the floor listing itself. Also the only thing that can tell an unknown symbol apart from a real empty collection.
- Default
true
collectLastSaleboolean
Fetch the last sale
One extra request per collection for the most recent completed sale — price, time, buyer, seller and mint. Off by default because the stats call already carries volume and sale counts.
- Default
false
collectAttributesboolean
Fetch trait summary
One extra request per collection for the trait schema and the cheapest live listing per trait value. Large collections return a few hundred trait values.
- Default
false
NFTs
collectNftsboolean
Also collect NFTs
Writes one row per live listing to crypto_magiceden_nfts, in addition to the collection rows. Off by default: a single large collection is tens of thousands of rows and hundreds of requests.
- Default
false
maxNftsPerCollectionnumber
Max NFTs per collection
Ceiling per collection, applied while paging so the walk stops rather than fetching and discarding. Listings page cleanly past 18,000 when a collection is that large; a ceiling that truncates warns.
- Default
2000- Range
- 1 to 100000
Advanced
requestDelayMsnumber
Delay between requests (ms)
Added to the ~850ms each request already takes. 102 consecutive requests at this pace drew no rate limiting when measured; lowering it further trades warnings for very little speed.
- Default
250- Range
- 0 to 30000
Input as JSON Schema
The same schema the API validates against. Use it as a tool input schema or to generate a typed client.
{ "type": "object", "additionalProperties": false, "properties": { "source": { "type": "string", "enum": [ "auto", "ranked", "catalog" ], "title": "Dataset", "description": "The ranked leaderboard carries market cap, total supply, owner count and a real ordering, but only for the ~3,900 collections it tracks. The catalog covers every collection Magic Eden has indexed, in an undocumented order, with no money columns at all.", "default": "auto" }, "collectionSymbols": { "type": "array", "items": { "type": "string" }, "maxItems": 1000, "title": "Collection symbols", "description": "Leave empty to discover collections from the chosen dataset. Naming symbols skips discovery entirely and refreshes exactly those rows.", "default": [], "examples": [ "okay_bears" ] }, "maxCollections": { "type": "number", "minimum": 1, "maximum": 10000, "title": "Max collections per run", "description": "Ceiling on discovered collections. The ranked universe is about 3,900 at the 30d window and about 370 at 1d; the catalog reaches roughly 30,000 before it refuses deeper offsets. A ceiling that truncates always warns.", "default": 500 }, "window": { "type": "string", "enum": [ "10m", "1h", "6h", "1d", "7d", "30d" ], "title": "Leaderboard window", "description": "Scopes the volume and sales columns on the ranked path, and also how many collections that path returns — 30d covers ~3,900, 1d only ~370. Ignored on the catalog path.", "default": "30d" }, "sort": { "type": "string", "enum": [ "marketCap", "volume", "volumePercentageChange", "sales", "salesPercentageChange", "totalSales", "floorPrice", "floorPricePercentageChange", "topOffer", "listedOverSupply", "ownerPercentage", "pending", "ownerCount", "listedCount", "minted", "mintedVolume" ], "title": "Leaderboard ranking", "description": "Which column the leaderboard ranks by, and therefore what `market_cap_rank` on the row is a rank of. Ranked path only.", "default": "marketCap" }, "verifiedOnly": { "type": "boolean", "title": "Verified collections only", "description": "Sends the marketplace’s own quality filter on the ranked path. Off by default because it barely moves the result — 370 collections became 367 when it was measured — while `is_verified` is recorded on every row anyway.", "default": false }, "collectStats": { "type": "boolean", "title": "Fetch per-collection stats", "description": "One extra request per collection for 24h/7d/30d volume, sale counts, average prices, the best standing offer and the floor listing itself. Also the only thing that can tell an unknown symbol apart from a real empty collection.", "default": true }, "collectLastSale": { "type": "boolean", "title": "Fetch the last sale", "description": "One extra request per collection for the most recent completed sale — price, time, buyer, seller and mint. Off by default because the stats call already carries volume and sale counts.", "default": false }, "collectAttributes": { "type": "boolean", "title": "Fetch trait summary", "description": "One extra request per collection for the trait schema and the cheapest live listing per trait value. Large collections return a few hundred trait values.", "default": false }, "collectNfts": { "type": "boolean", "title": "Also collect NFTs", "description": "Writes one row per live listing to crypto_magiceden_nfts, in addition to the collection rows. Off by default: a single large collection is tens of thousands of rows and hundreds of requests.", "default": false }, "maxNftsPerCollection": { "type": "number", "minimum": 1, "maximum": 100000, "title": "Max NFTs per collection", "description": "Ceiling per collection, applied while paging so the walk stops rather than fetching and discarding. Listings page cleanly past 18,000 when a collection is that large; a ceiling that truncates warns.", "default": 2000 }, "requestDelayMs": { "type": "number", "minimum": 0, "maximum": 30000, "title": "Delay between requests (ms)", "description": "Added to the ~850ms each request already takes. 102 consecutive requests at this pace drew no rate limiting when measured; lowering it further trades warnings for very little speed.", "default": 250 } }}Run, poll, read rows
The run is asynchronous. Poll it until it reaches a terminal status and its billing is paid, then page through its rows. Until then the rows endpoint answers 409 (still queued or running) or 402 (payment required).
RUN_ID=$(curl -s -X POST "https://api.pandorlabs.com/v1/scrapers/magiceden-collections/runs" \ -H "Authorization: Bearer $PANDORLABS_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "source": "auto", "collectionSymbols": [ "okay_bears" ], "maxCollections": 500, "window": "30d" } }' | jq -r '.id')while true; do RUN=$(curl -s "https://api.pandorlabs.com/v1/runs/$RUN_ID" \ -H "Authorization: Bearer $PANDORLABS_KEY") STATUS=$(echo "$RUN" | jq -r '.status') BILLING=$(echo "$RUN" | jq -r '.billing.status') case "$STATUS" in queued|running) ;; *) [ "$BILLING" != "pending" ] && break ;; esac sleep 5doneecho "$RUN" | jq '{status, rowCount, billing}'curl -s "https://api.pandorlabs.com/v1/runs/$RUN_ID/rows?page=1&limit=100" \ -H "Authorization: Bearer $PANDORLABS_KEY"See the API reference for pagination, cancelling and listing runs.
Output fields
Rows have no fixed schema; these are the fields observed in the rows collected so far. Coverage is the share of rows where the field is present.
| Field | Type | Coverage |
|---|---|---|
| avg_price_24h_sol | number | 35% |
| avg_price_30d_sol | number | 90% |
| avg_price_7d_sol | number | 59% |
| categories | array | 54% |
| collection_known | boolean | 100% |
| collection_symbol | string | 100% |
| contract_address | string | 56% |
| currency | string | 86% |
| currency_usd_rate | number | 86% |
| description | string | 14% |
| discord | string | 1% |
| floor_delta_7d | number | 81% |
| floor_nft_listing_type | string | 81% |
| floor_nft_mint | string | 81% |
| floor_nft_owner | string | 81% |
| floor_nft_price_sol | number | 81% |
| floor_price_lamports | number | 91% |
| floor_price_pct_change | number | 79% |
| floor_price_pct_change_7d | number | 81% |
| floor_price_sol | number | 91% |
| has_cnfts | boolean | 14% |
| has_inscriptions | boolean | 86% |
| highest_offer_excl_royalties_sol | number | 77% |
| highest_offer_sol | number | 82% |
| image_url | string | 98% |
| is_badged | boolean | 14% |
| is_compressed | boolean | 86% |
| is_ocp | boolean | 14% |
| is_verified | boolean | 57% |
| last_sale_buyer | string | 33% |
| last_sale_mint | string | 33% |
| last_sale_price_sol | number | 33% |
| last_sale_seller | string | 33% |
| last_sale_signature | string | 33% |
| last_sale_source | string | 33% |
| listed_count | number | 100% |
| market_cap_rank | number | 86% |
| market_cap_sol | number | 86% |
| market_cap_usd | number | 86% |
| minted | number | 86% |
| minted_volume_sol | number | 86% |
| name | string | 99% |
| owner_count | number | 86% |
| sales_24h | number | 99% |
| sales_30d | number | 99% |
| sales_7d | number | 99% |
| sales_window | number | 86% |
| sales_window_pct_change | number | 64% |
| seller_fee_basis_points | number | 86% |
| stats_fields_missing | boolean | 100% |
| supply | number | 86% |
| taker_fee_basis_points | number | 86% |
| total_sales | number | 86% |
| total_spins_24h | number | 100% |
| trait_floors | array | 39% |
| trait_types | array | 39% |
| trait_value_count | number | 34% |
| unique_owner_ratio | number | 86% |
| volume_24h_sol | number | 35% |
| volume_30d_sol | number | 90% |
| volume_7d_sol | number | 59% |
| volume_window_pct_change | number | 64% |
| volume_window_sol | number | 86% |
| website | string | 4% |
| window | string | 86% |
| created_at | date | 100% |
| first_seen_at | date | 100% |
| last_sale_at | date | 33% |
| last_seen_at | date | 100% |
| updated_at | date | 100% |
| _id | string | 100% |
