Overview
Every call starts with
https://mudwatercards.com/api/v1/
The API answers with what the site's own pages show: the same copies of OpenSea, Magic Eden, eBay, BAYC P2P and Grimy’s Card Shack, the same sales log, the same counts of copies on the Solana chain. Nothing is worked out for the API alone, so a number here always matches the card page it links to, and a card page is where a reader can see the listings and sales behind it.
It is meant for the things collectors build around the cards: a scanner app that needs a price for the card it just read, a rip stream showing what a pull is worth, a Discord bot answering !price gary, a spreadsheet. Four endpoints:
- GET
/cardsfind a card by name, number or trait - GET
/cards/{id}one card's facts, print runs, copies on chain, art - GET
/cards/{id}/pricesits digital and physical prices, last sales and medians, per finish - GET
/pricesa digital and a physical value per card and finish for the whole set, in one file
Quick start, from a shell or a web page:
curl "https://mudwatercards.com/api/v1/cards?q=gary"
fetch("https://mudwatercards.com/api/v1/cards/49/prices")
.then(r => r.json())
.then(p => console.log(p.finishes.base.digital.value?.usd, p.finishes.base.physical.lowest?.usd));
Access & limits
No key. Every endpoint is open. Each IP address may make 60 requests an hour that reach the server; a repeat of the same URL within 5 minutes is answered from the cache and does not count. The headers on every answer:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=300
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-RateLimit-Limit, Retry-After
X-RateLimit-Limit: 60
Answers are shared through a cache, so none carries a count of what you have left (it would be someone else’s): the limit is the one figure, and the 429 is the signal. Past the limit:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Cache-Control: no-store
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-RateLimit-Limit, Retry-After
Retry-After: 1380
X-RateLimit-Limit: 60
{
"error": {
"code": "rate_limited",
"message": "60 requests an hour per IP address. Try again in 23 minutes."
}
}
Retry-After is in seconds. If your app needs more, say so: a key with a higher limit is the planned next step, and a key will never change what an endpoint returns.
Say who you are, if you like. An optional app parameter naming your app (app=tiltrips-overlay), or on a server-side client a User-Agent that names it, is logged and never required. It lets us see what is used and warn the maker before anything changes; to be warned, tell @KaijuTheApe what you built and how to reach you. Keep one spelling of the name: the cache is per URL, app included. There is no custom request header for it, because a browser would have to preflight every call.
From a browser. Every answer carries Access-Control-Allow-Origin: * and exposes X-RateLimit-Limit and Retry-After to scripts, so a web page or an OBS browser overlay can call the API directly, with nothing to preflight. Only GET is served.
Caching. Responses are Cache-Control: public, max-age=300. Prices move about every 5 to 30 minutes on the site itself, so polling faster than that only returns the same answer. A copy of a feed that is past its refresh time is served as it is, with its as_of, never refused.
Conventions
id- A card’s id is its set and number,
swamp-origins:049, orapefest-2026:prm-01for a promo: unique across every set there will be, and the same shape as the card’s page address. The short forms49,049andprm-01are taken wherever an id is and mean the Swamp Origins card or the ApeFest promo, today and for as long as v1 answers; a later set’s cards are reached by their full id. finishbase,foilorserialized. Serialized data exists only for the cards that have a Serialized edition on chain; promo cards have one finish,any, since nobody is sure which promos exist in both.venueopensea,magiceden,ebay,p2p(BAYC P2P) orgrimys(Grimy’s Card Shack).Case- Ids, parameters and their values are case-insensitive:
rarity=legendary,PRM-01. Values in answers are as the card prints them,"Legendary","Club"; finish, venue and basis keys are lower case. usd- Dollars as a JSON number (
512.0,498.12), never a string. A native amount sits beside it where there is one:solfor digital copies,apefor BAYC P2P. estimatetruewherever the site prints a “~”: an APE price at today’s rate, a store that sets shipping at checkout, a seller abroad whose price is converted.falseis a price you would pay as printed.Physical pricesusdis what the copy costs: the item plus its stated shipping, and no sales tax (the site adds a visitor’s own state’s tax, which an API cannot know).itemandshippingare its two parts;shippingis0.00when it is free or included, andnullwithshipping_at_checkouttrue when the seller sets it at checkout, in which caseusdis the item alone and anestimate.Times- ISO 8601 in UTC,
2026-11-11T14:02:00Z; a sale known by its day alone is a date,2026-11-02. Every priced answer carriesas_of: when each feed was last read. null- A value we do not have is
null; the field is still there. A card with nothing listed has"lowest": nulland"listed": 0, not a missing key. In CSV, null is an empty field. Versions- The path carries the version,
/api/v1/. Within v1 fields may be added and are never renamed or removed. A change that would break a client becomes/api/v2/, and v1 keeps answering. The same description is machine-readable at/api/v1/openapi.json. attribution- Every answer with a price carries a credit line and the page to link, for apps that want to show where a figure came from, see Attribution.
Card search
/api/v1/cardsFinds cards by name, number or trait. With no parameters it returns all 128 cards, about 40 KB, which an app can keep and search itself. Matching is by whole word from its start, so gary, good boy and 49 all find #049; the printed spellings that differ from the chain’s (“Lofi on the Deck” and “Dock”) both work.
| Parameter | Type | Meaning |
|---|---|---|
q | string | Name, number, promo id or a word of the name. A number alone matches that card only. |
set | string | swamp-origins or apefest-2026. |
rarity | string | common, uncommon, rare, prestige or legendary. |
type | string | The card type as printed on the card, e.g. club. |
artist | string | The artist credit as printed, e.g. chomps. |
{
"count": 1,
"data": [
{
"id": "swamp-origins:049",
"set": "swamp-origins",
"number": 49,
"label": "#049",
"name": "Good Boy Gary",
"rarity": "Legendary",
"type": "Club",
"subset": "Dog Food",
"artist": "Chomps",
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary",
"thumb": "https://mudwatercards.com/cards/049-good-boy-gary-480.webp"
}
]
}
count- How many cards matched. An empty match is
"count": 0with an emptydata, not an error. data[].id- The id to use in the other two card endpoints, unique across sets; the short forms (
49,prm-01) work there too for today’s two sets. data[].thumb- The card’s Base art at 480 × 666, on this site. The full-size art and the other finishes are in card info.
Card info
/api/v1/cards/{id}Everything known about one card that is not a price: its traits and edge values from Yuga’s card data, the fixed print run of each finish, how many copies exist on the Solana chain and how many wallets hold one, and the art per finish from this site’s own copies.
| Parameter | Type | Meaning |
|---|---|---|
id | path | swamp-origins:049, or the short forms 49, 049, prm-01. An unknown id answers 404. |
GET /api/v1/cards/49
{
"id": "swamp-origins:049",
"set": "swamp-origins",
"number": 49,
"label": "#049",
"name": "Good Boy Gary",
"aliases": [],
"rarity": "Legendary",
"level": 5,
"type": "Club",
"subset": "Dog Food",
"artist": "Chomps",
"edges": { "top": 7, "right": 9, "bottom": 7, "left": 3 },
"finishes": {
"base": {
"printed": 1320,
"on_chain": 50,
"holders": 43,
"image": "https://mudwatercards.com/cards/049-good-boy-gary.webp",
"thumb": "https://mudwatercards.com/cards/049-good-boy-gary-480.webp"
},
"foil": {
"printed": 136,
"on_chain": 4,
"holders": 4,
"image": "https://mudwatercards.com/cards/049-good-boy-gary-foil.webp",
"thumb": "https://mudwatercards.com/cards/049-good-boy-gary-foil-480.webp"
},
"serialized": {
"printed": 1,
"on_chain": 0,
"holders": 0,
"image": "https://mudwatercards.com/cards/049-good-boy-gary-serialized.webp",
"thumb": "https://mudwatercards.com/cards/049-good-boy-gary-serialized-480.webp"
}
},
"on_chain_as_of": "2026-11-11T03:44:00Z",
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary",
"opensea": "https://opensea.io/collection/mudwater-cards?search[stringTraits][0][name]=Card%20Number&search[stringTraits][0][values][0]=49",
"attribution": {
"text": "Card data by MudwaterCards.com",
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary"
}
}
level- A number, 1 to 5: on every set card it is the rarity’s rank (Common 1 … Legendary 5).
edges- The four edge values printed on the card.
finishes.*.printed- The fixed print run, the official “printed” figure. How many CAN exist.
finishes.*.on_chain- Copies that exist on the Solana chain right now, i.e. pulled from packs so far;
on_chain_as_ofsays when they were counted (every few hours). finishes.*.holders- Wallets holding at least one copy of that finish.
finishes.*.image- The full-size rendering (1621 × 2250) on this site;
thumbthe 480-wide copy. A Serialized rendering is present only where Yuga has published one. Promo cards- Carry
"set": "apefest-2026", one finish keyedanywithprinted,on_chain,holdersandimageallnull(nothing is published, promos are not on chain, and the site has a scan of each rather than a rendering:thumb),openseanull, and the traits read off the printed card.
Card prices
/api/v1/cards/{id}/pricesOne card’s market, per finish and per side. The digital side is OpenSea and Magic Eden: the lowest copy, the last sale, 7- and 30-day medians, and one value. The physical side is eBay (US sellers), BAYC P2P and Grimy’s Card Shack: the lowest printed copy, the last confirmed sale, the 7-day median, and its own value. The two sides sit far apart on many cards (Gary’s Foil: $1,377 digital against $118 printed), so show the side your users are looking at: the printed card in their hand, or the pack they opened on chain. Pool and self-trades are left out of the sales, as on the card pages.
| Parameter | Type | Meaning |
|---|---|---|
id | path | swamp-origins:049, or the short forms 49, 049, prm-01. |
finish | string | Optional: base, foil or serialized to get that finish alone. Default: every finish the card has. |
{
"id": "swamp-origins:049",
"set": "swamp-origins",
"number": 49,
"label": "#049",
"name": "Good Boy Gary",
"rarity": "Legendary",
"rules": {
"digital": "7-day median sale with 3+ sales; else the 30-day median with 3+; else 1-2 sales in 30 days blended with the lowest listing; else the lowest listing with 3+ listed; else the rarity's median in that finish; never above the lowest listing",
"physical": "7-day median sale with 3+ sales; else the 30-day median with 3+; else 1-2 sales in 30 days blended with the lowest copy; else the lowest copy; never above it"
},
"finishes": {
"base": {
"digital": {
"value": { "usd": 512.0, "basis": "median_7d", "sales": 5 },
"lowest": {
"usd": 530.4,
"sol": 6.1,
"venue": "opensea",
"url": "https://opensea.io/item/solana/5aB3…"
},
"listed": 3,
"last_sold": { "usd": 498.12, "sol": 5.73, "at": "2026-11-10T22:08:00Z", "venue": "opensea" },
"median_7d": { "usd": 512.0, "sales": 5 },
"median_30d": { "usd": 505.5, "sales": 19 }
},
"physical": {
"value": { "usd": 455.0, "basis": "lowest_listing", "sales": null, "estimate": false },
"lowest": {
"usd": 455.0,
"item": 450.0,
"shipping": 5.0,
"shipping_at_checkout": false,
"estimate": false,
"venue": "ebay",
"condition": "Near Mint",
"country": "US",
"url": "https://mudwatercards.com/?go=ebay&card=49&base=1"
},
"listed": 2,
"last_sold": { "usd": 425.0, "at": "2026-11-04T19:20:00Z", "venue": "ebay", "estimate": false },
"median_7d": null
}
},
"foil": {
"digital": {
"value": { "usd": 1313.5, "basis": "few_sales", "sales": 1 },
"lowest": {
"usd": 1377.0,
"sol": 15.83,
"venue": "opensea",
"url": "https://opensea.io/item/solana/9kQ1…"
},
"listed": 3,
"last_sold": { "usd": 1250.0, "sol": 14.21, "at": "2026-11-02T03:15:00Z", "venue": "opensea" },
"median_7d": null,
"median_30d": null
},
"physical": {
"value": { "usd": 118.0, "basis": "lowest_listing", "sales": null, "estimate": true },
"lowest": {
"usd": 118.0,
"item": 118.0,
"shipping": 0.0,
"shipping_at_checkout": false,
"ape": 120.0,
"estimate": true,
"venue": "p2p",
"condition": null,
"country": null,
"url": "https://mudwatercards.com/?go=p2p&card=49&foil=1"
},
"listed": 1,
"last_sold": null,
"median_7d": null
}
},
"serialized": {
"digital": { "value": null, "lowest": null, "listed": 0, "last_sold": null, "median_7d": null, "median_30d": null },
"physical": { "value": null, "lowest": null, "listed": 0, "last_sold": null, "median_7d": null }
}
},
"as_of": {
"digital": "2026-11-11T14:02:00Z",
"physical": "2026-11-11T13:51:00Z",
"sales": "2026-11-11T14:02:00Z"
},
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary",
"attribution": {
"text": "Prices by MudwaterCards.com",
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary"
}
}
rules- The two value rules, as text, in every answer, so a reader of the raw JSON knows what the figures are.
digital.value- One digital figure to show when there is room for one, from sales before asks: the 7-day median sale with three or more sales; else the 30-day median with three or more; else one or two sales in 30 days blended with the lowest listing (the ask counts as one more opinion); else the lowest listing when three or more copies are listed; else the median of that rarity in that finish. Never above the lowest listing, which is what the card costs right now.
basissays which step gave it,median_7d,median_30d,few_sales,lowest_listingorrarity_median;salesis the count behind a median or a blend,nullotherwise. The whole isnullwhen nothing prices the finish. physical.value- The printed side’s figure on the same ladder: the 7-day median printed sale with three or more; else the 30-day median; else one or two sales blended with the lowest printed copy; else the lowest copy, however many are listed; never above it.
basismedian_7d,median_30d,few_salesorlowest_listing;estimatetrue when the lowest copy’s figure is one and took part. Printed sales are still few, so expectlowest_listingon most cards. digital.lowest- The cheapest copy on OpenSea or Magic Eden, whichever wins, with its own listing’s address.
listed, beside it, counts both markets. digital.last_sold- The newest sale on chain of that finish, at the SOL rate of the moment it sold.
physical.lowest- The cheapest printed copy of that finish among US eBay sellers, BAYC P2P and Grimy’s: the one the card page’s Live prices row shows. Its
urlgoes through this site’s redirect, which checks the listing is still up and lands on it. physical.listed- Every printed copy of that finish for sale across those markets, sellers abroad included.
physical.last_sold- The newest confirmed sale of a printed copy: an eBay sale or a BAYC P2P sale.
estimateis true when eBay did not state the shipping. A sale the site found through eBay’s sold search is known by its day alone, soatis then a date,2026-11-02; one the site’s own check caught is timed within about half an hour. serialized- Present only for the cards with a Serialized edition: digital asks and on-chain sales, no medians and no
value(a serial-numbered copy is its own item, and a rule for a crowd fits it badly). Promo cards- One finish keyed
any;digitalis there with every field null (promos are printed only) andphysicalis on the same rules.
All values
/api/v1/pricesThe whole set in one file: one row per card and finish with its digital and its physical value, the rules that gave them, the lowest digital and printed copies and the last sale of each kind. This is the call for a stream overlay or a bot: fetch it once, look pulls up locally, fetch again when it is a few minutes old. 120 cards × Base and Foil, then the 8 promos as any: 248 rows, about 80 KB.
| Parameter | Type | Meaning |
|---|---|---|
finish | string | Optional: base or foil for the set cards’ rows of that finish, any for the promos alone. Without it, all 248 rows. |
format | string | Optional: csv for the same rows as comma-separated text with a header line, for a spreadsheet. A field holding a comma or a quote is quoted (RFC 4180); null is an empty field. |
GET /api/v1/prices
{
"as_of": {
"digital": "2026-11-11T14:02:00Z",
"physical": "2026-11-11T13:51:00Z",
"sales": "2026-11-11T14:02:00Z"
},
"rules": {
"digital": "7-day median sale with 3+ sales; else the 30-day median with 3+; else 1-2 sales in 30 days blended with the lowest listing; else the lowest listing with 3+ listed; else the rarity's median in that finish; never above the lowest listing",
"physical": "7-day median sale with 3+ sales; else the 30-day median with 3+; else 1-2 sales in 30 days blended with the lowest copy; else the lowest copy; never above it"
},
"count": 248,
"data": [
{
"id": "swamp-origins:049", "set": "swamp-origins", "number": 49, "name": "Good Boy Gary", "rarity": "Legendary", "finish": "base",
"digital_value": 512.0, "digital_basis": "median_7d", "digital_low": 530.4, "digital_last_sold": 498.12,
"physical_value": 455.0, "physical_basis": "lowest_listing", "physical_low": 455.0, "physical_venue": "ebay", "physical_last_sold": 425.0,
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary"
},
{
"id": "swamp-origins:049", "set": "swamp-origins", "number": 49, "name": "Good Boy Gary", "rarity": "Legendary", "finish": "foil",
"digital_value": 1313.5, "digital_basis": "few_sales", "digital_low": 1377.0, "digital_last_sold": 1250.0,
"physical_value": 118.0, "physical_basis": "lowest_listing", "physical_low": 118.0, "physical_venue": "p2p", "physical_last_sold": null,
"url": "https://mudwatercards.com/swamp-origins/049-good-boy-gary?f=foil"
},
{
"id": "swamp-origins:073", "set": "swamp-origins", "number": 73, "name": "The Spare", "rarity": "Common", "finish": "base",
"digital_value": 1.2, "digital_basis": "median_7d", "digital_low": 1.18, "digital_last_sold": 1.2,
"physical_value": 5.91, "physical_basis": "lowest_listing", "physical_low": 5.91, "physical_venue": "ebay", "physical_last_sold": null,
"url": "https://mudwatercards.com/swamp-origins/073-the-spare"
},
{
"id": "apefest-2026:prm-01", "set": "apefest-2026", "number": null, "name": "Away Game", "rarity": "Uncommon", "finish": "any",
"digital_value": null, "digital_basis": null, "digital_low": null, "digital_last_sold": null,
"physical_value": 95.0, "physical_basis": "lowest_listing", "physical_low": 95.0, "physical_venue": "ebay", "physical_last_sold": 80.0,
"url": "https://mudwatercards.com/apefest-2026/prm-01-away-game"
}
],
"attribution": {
"text": "Prices by MudwaterCards.com",
"url": "https://mudwatercards.com/"
}
}
id,set,number,name,rarity,finish,digital_value,digital_basis,digital_low,digital_last_sold,physical_value,physical_basis,physical_low,physical_venue,physical_last_sold,url
swamp-origins:049,swamp-origins,49,Good Boy Gary,Legendary,base,512.00,median_7d,530.40,498.12,455.00,lowest_listing,455.00,ebay,425.00,https://mudwatercards.com/swamp-origins/049-good-boy-gary
swamp-origins:049,swamp-origins,49,Good Boy Gary,Legendary,foil,1313.50,few_sales,1377.00,1250.00,118.00,lowest_listing,118.00,p2p,,https://mudwatercards.com/swamp-origins/049-good-boy-gary?f=foil
digital_value- The card’s
digital.valuefrom card prices, on the same rule;digital_basisnames it. physical_value- Its
physical.value;physical_basisnames the rule. digital_low- The lowest digital listing in dollars, or
nullwith nothing listed. physical_low- The cheapest printed copy, item plus stated shipping, no tax;
nullwith none for sale. physical_venue- Where that copy is:
ebay,p2porgrimys. A BAYC P2P or Grimy’s figure is an estimate (the site prints “~”); eBay’s is the price as listed. digital_last_sold- The newest sale on chain of that card and finish, in dollars.
physical_last_sold- The newest confirmed sale of a printed copy, in dollars.
Serialized- Not in this file: a serial-numbered copy is its own item and a rule for a crowd fits it badly. Card prices has its asks and sales.
Errors
An error is a JSON body of one shape with the matching HTTP status, never an HTML page.
{
"error": {
"code": "not_found",
"message": "No card \"149\". Set cards are 1 to 120, promos prm-01 to prm-08; ids read swamp-origins:049 or apefest-2026:prm-01."
}
}
| Status | Code | When |
|---|---|---|
| 400 | bad_request | A parameter has a value the endpoint does not take, e.g. finish=gold. |
| 404 | not_found | No card has that id, or the path is not an endpoint. |
| 405 | method_not_allowed | Anything but GET (or HEAD). |
| 429 | rate_limited | Over the hourly limit; Retry-After says how long to wait. |
| 503 | unavailable | No copy of a feed the answer needs exists yet, which only happens right after a deploy. Retry in a minute. (A copy that is merely old is served, with its as_of.) |
Terms
By using the API you agree to these. The prices and listings come from third-party marketplaces and stay subject to their terms, eBay’s API License Agreement included, with eBay as a third-party beneficiary of this agreement. The data is for showing to people: not for training AI models, and not for reselling as a feed. Access may end at any time, for everyone or for one app, as may any one marketplace’s part of it. Listings change by the minute, so nothing here is a guarantee of a price. Mudwater, Swamp Origins and BAYC are trademarks of Yuga Labs; this site is fan-made and affiliated with neither Yuga Labs nor any marketplace.
Attribution
The API is free, with no condition on it. One ask: where a price from it shows, a small “Prices by MudwaterCards.com” with a link to the card’s page would mean a lot. It lets your users see the listings and sales behind the number, and it is how the site that keeps these feeds running gets found. Every priced answer carries the text and the address in its attribution field, so it is one line of code; mark the link however you like (plain, nofollow, sponsored).
And tell @KaijuTheApe what you built: it gets a row on the Links page, and a heads-up before anything here changes.
Not in v1
Left out on purpose, so what ships is small and stays up. Ask if one of these would matter to you.
- Sealed product: booster box, pack and case prices (the Sealed pages have them).
- Sales history per card, beyond the last sale and the medians.
- What a wallet holds.
- Sales tax: a
stateparameter for an all-in physical price. - Keys with higher limits, webhooks or alerts, anything that writes.
Reference as of Nov 11, 2026. Machine-readable: openapi.json.