[{"data":1,"prerenderedAt":4645},["ShallowReactive",2],{"blog-build-one-piece-card-price-tracker-berrywallet-api-tutorial":3},{"id":4,"title":5,"author":6,"body":7,"category":4628,"date":4629,"description":4630,"extension":4631,"image":4632,"imageAlt":4633,"imageCredit":6,"meta":4634,"navigation":1112,"path":4635,"readTime":4636,"seo":4637,"stem":4638,"tags":4639,"__hash__":4644},"content\u002Fblog\u002Fone-piece\u002F2026\u002F10\u002Fbuild-one-piece-card-price-tracker-berrywallet-api-tutorial.md","One Piece Price Tracker: Build It with the BerryWallet API (Node.js)","PokéWallet Team",{"type":8,"value":9,"toc":4602},"minimark",[10,15,30,68,85,93,106,111,234,237,279,281,286,289,312,330,338,439,451,454,458,499,502,561,574,578,599,602,610,618,632,652,656,662,694,697,986,989,1025,1028,1234,1257,1266,1292,1554,1576,1598,1602,1606,2350,2374,2378,2392,2586,2589,2593,4055,4058,4064,4067,4079,4083,4145,4151,4155,4166,4220,4226,4230,4233,4321,4331,4339,4341,4345,4349,4355,4359,4387,4391,4404,4408,4425,4429,4435,4439,4454,4458,4475,4479,4495,4497,4507,4509,4513,4516,4558,4563,4591,4593,4598],[11,12,14],"h1",{"id":13},"build-a-one-piece-card-price-tracker-with-the-berrywallet-api","Build a One Piece Card Price Tracker with the BerryWallet API",[16,17,19,20],"div",{"style":18},"background: rgba(14, 165, 233, 0.15); border-left: 4px solid #0ea5e9; padding: 1rem 1.5rem; margin: 1.5rem 0; border-radius: 4px;","\n  ",[21,22,24,25,29],"p",{"style":23},"margin: 0; font-size: 0.9rem; color: #e0e0e0;","\n    ",[26,27,28],"strong",{},"🔵 IN THIS GUIDE:"," A dependency-free Node.js price tracker for One Piece cards. It pulls TCGPlayer and CardMarket prices from the BerryWallet API, keeps its own price history and alerts you when a card moves. Every request and field name below comes from real API responses captured on October 4, 2026.\n  ",[31,32,33],"blockquote",{},[21,34,35,38,39,43,44,47,48,51,52,55,56,59,60,63,64,67],{},[26,36,37],{},"Quick Answer:"," To track One Piece card prices, call ",[40,41,42],"code",{},"GET https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fprices?set_code=OP01"," with your key in the ",[40,45,46],{},"X-API-Key"," header. You get every card variant in the set with TCGPlayer prices (USD) and CardMarket prices (EUR) in one response: ",[26,49,50],{},"159 rows"," for Romance Dawn alone, out of ",[26,53,54],{},"78 sets"," in the catalog. Store a snapshot on each run, compare it with the previous one, and alert on the change. On October 4, 2026, the Zoro OP01-001 Parallel leader showed a TCGPlayer market price of ",[26,57,58],{},"$619.64"," and a CardMarket trend of ",[26,61,62],{},"€610.16",", while the regular Zoro was at ",[26,65,66],{},"$2.06",". The full tracker is about 120 lines of plain JavaScript, needs Node 18+ and has no npm dependencies.",[21,69,70,71,76,77,80,81,84],{},"This is the One Piece version of our ",[72,73,75],"a",{"href":74},"\u002Fblog\u002Fbuild-pokemon-card-price-tracker-pokewallet-api-tutorial","Pokémon card price tracker tutorial",". It's the same idea, but the data has its own quirks. One Piece variants share card numbers, many promos have no CardMarket listing, and set codes range from ",[40,78,79],{},"OP01"," to ",[40,82,83],{},"ST-04 PRE",". A tracker that ignores these quirks will report wrong prices without any visible error. The code below handles all three.",[21,86,87,88,92],{},"We focus on one job: a script you run on a schedule that tells you when a card on your watchlist has moved. We skip the UI and the database server at first. A watchlist file, a history file and a cron line cover most of what collectors and small shop owners need. Once that works, the ",[72,89,91],{"href":90},"#going-to-production","production section"," covers what to change when it grows.",[21,94,95,96,100,101,105],{},"If you haven't used the API yet, ",[72,97,99],{"href":98},"\u002Fblog\u002Fone-piece-card-game-api-guide-real-time-prices","our overview of fetching real-time OPCG card prices"," explains the data model. For how BerryWallet compares with other options, see ",[72,102,104],{"href":103},"\u002Fblog\u002Fberrywallet-vs-one-piece-api-comparison-2026","BerryWallet vs one-piece-api.com",".",[107,108,110],"h3",{"id":109},"key-takeaways","Key Takeaways",[112,113,114,141,151,158,172,196,207],"ul",{},[115,116,117,118,128,129,132,133,136,137,140],"li",{},"🔑 ",[26,119,120,121,124,125,105],{},"Track the ",[40,122,123],{},"id"," field, not ",[40,126,127],{},"card_number"," ",[40,130,131],{},"EB02-010"," points to two different Luffy products: one at ",[26,134,135],{},"$1,534.62"," and one at ",[26,138,139],{},"$30.99",". Keying on the card number mixes them up.",[115,142,143,144,128,147,150],{},"📦 ",[26,145,146],{},"Fetch prices one set at a time.",[40,148,149],{},"\u002Fop\u002Fprices?set_code=OP01"," returns the whole set in one call. A 50-card watchlist from one set costs one or two requests instead of 50.",[115,152,153,154,157],{},"💱 ",[26,155,156],{},"Never compare TCGPlayer and CardMarket numbers directly."," TCGPlayer is in USD and CardMarket is in EUR. $619.64 vs €610.16 doesn't mean the markets agree until you convert one of them.",[115,159,160,161,167,168,171],{},"🕳 ",[26,162,163,164,105],{},"Expect ",[40,165,166],{},"cardmarket: null"," Both Luffy promos in our sample have no CardMarket data. Code that reads ",[40,169,170],{},".cardmarket.prices.trend"," without a null check will crash on the first promo.",[115,173,174,175,185,186,188,189,192,193,195],{},"📈 ",[26,176,177,178,181,182,105],{},"Use ",[40,179,180],{},"market_price"," for alerts, not ",[40,183,184],{},"high_price"," The Dodgers Luffy has a ",[40,187,184],{}," of ",[26,190,191],{},"$16,999"," against a market price of ",[26,194,135],{},". One overpriced listing shouldn't trigger an alert.",[115,197,198,199,206],{},"⏱ ",[26,200,201,202,205],{},"Save a snapshot only when ",[40,203,204],{},"updated_at"," changes."," Running the script more often than the data refreshes fills your history with duplicate rows and flattens your charts.",[115,208,209,210,221,222,225,226,229,230,233],{},"🚨 ",[26,211,212,213,216,217,220],{},"CardMarket's ",[40,214,215],{},"avg1"," vs ",[40,218,219],{},"avg30"," gives you a free spike detector."," Zoro Parallel's 1-day average (",[26,223,224],{},"€770",") was ",[26,227,228],{},"25.4%"," above its 30-day average (",[26,231,232],{},"€613.93","). That's often the first sign of a move.",[235,236],"hr",{},[16,238,19,240,19,244],{"style":239},"background: linear-gradient(135deg, rgba(107, 76, 154, 0.1), rgba(107, 76, 154, 0.05)); border: 1px solid rgba(107, 76, 154, 0.3); border-radius: 8px; padding: 1.5rem; margin: 2rem 0; text-align: center;",[21,241,243],{"style":242},"margin: 0 0 1rem 0; font-weight: 600; font-size: 1.1rem; color: #e0e0e0;","⚡ Quick Navigation",[16,245,24,247,24,252,24,256,24,260,24,264,24,268,24,272,24,275,19],{"style":246},"display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center;",[72,248,251],{"href":249,"style":250},"#what-you-will-build","background: rgba(107, 76, 154, 0.2); color: #B794F6; padding: 0.5rem 1rem; border-radius: 6px; text-decoration: none; font-size: 0.9rem; border: 1px solid rgba(107, 76, 154, 0.4);","🎯 What You Build",[72,253,255],{"href":254,"style":250},"#prerequisites","📋 Prerequisites",[72,257,259],{"href":258,"style":250},"#step-1-get-your-api-key","🔑 API Key",[72,261,263],{"href":262,"style":250},"#step-2-the-first-request","📡 First Request",[72,265,267],{"href":266,"style":250},"#step-3-building-it-out","🛠 Build",[72,269,271],{"href":270,"style":250},"#handling-rate-limits","⏱ Rate Limits",[72,273,274],{"href":90,"style":250},"🚀 Production",[72,276,278],{"href":277,"style":250},"#frequently-asked-questions","❓ FAQ",[235,280],{},[282,283,285],"h2",{"id":284},"what-you-will-build","What You Will Build",[21,287,288],{},"You'll build a command-line tracker that does four things on each run:",[290,291,292,299,302,309],"ol",{},[115,293,294,295,298],{},"Reads a ",[40,296,297],{},"watchlist.json"," file listing the cards you care about, each with optional alert rules.",[115,300,301],{},"Fetches current prices, grouping cards by set so each set costs as few requests as possible. Cards whose set you don't know are looked up through search.",[115,303,304,305,308],{},"Adds a snapshot to ",[40,306,307],{},"history.json",", but only when the source data has actually refreshed.",[115,310,311],{},"Prints a price table and lists every alert that fired: target price reached, move since the last snapshot, or a short-term spike on CardMarket.",[21,313,314,315,318,319,322,323,325,326,329],{},"The project has three files: ",[40,316,317],{},"client.js"," (HTTP and retries), ",[40,320,321],{},"tracker.js"," (the logic) and ",[40,324,297],{}," (your cards). Run it by hand or from cron. The history file it builds is your price history, which matters because the One Piece endpoints don't include a history endpoint (see the ",[72,327,328],{"href":277},"FAQ",").",[21,331,332,333,337],{},"Our test case is Roronoa Zoro, OP01-001, the leader from ",[72,334,336],{"href":335},"\u002Fblog\u002Fone-piece-tcg-romance-dawn-op01-complete-guide-prices","Romance Dawn",". Its two versions differ in price by a factor of about 300, which makes it a good stress test for variant handling:",[339,340,341,366],"table",{},[342,343,344],"thead",{},[345,346,347,351,354,357,360,363],"tr",{},[348,349,350],"th",{},"Card",[348,352,353],{},"Variant",[348,355,356],{},"TCGPlayer market",[348,358,359],{},"TCGPlayer low",[348,361,362],{},"CardMarket trend",[348,364,365],{},"CardMarket 30-day avg",[367,368,369,389,406,424],"tbody",{},[345,370,371,375,378,380,383,386],{},[372,373,374],"td",{},"Roronoa Zoro (001)",[372,376,377],{},"Normal",[372,379,66],{},[372,381,382],{},"$1.49",[372,384,385],{},"€1.87",[372,387,388],{},"€2.09",[345,390,391,394,397,399,402,404],{},[372,392,393],{},"Roronoa Zoro (001) (Parallel)",[372,395,396],{},"Foil",[372,398,58],{},[372,400,401],{},"$695.01",[372,403,62],{},[372,405,232],{},[345,407,408,411,413,415,418,421],{},[372,409,410],{},"Monkey.D.Luffy (010) (Dodgers x ONE PIECE)",[372,412,396],{},[372,414,135],{},[372,416,417],{},"$1,599.95",[372,419,420],{},"— (no CardMarket data)",[372,422,423],{},"—",[345,425,426,429,431,433,435,437],{},[372,427,428],{},"Monkey.D.Luffy (Sound Loader – Luffy Edition 2025)",[372,430,377],{},[372,432,139],{},[372,434,139],{},[372,436,420],{},[372,438,423],{},[21,440,441],{},[442,443,444,445,447,448,450],"em",{},"Data from the BerryWallet API, captured October 4, 2026 (TCGPlayer ",[40,446,204],{}," 11:49, CardMarket ",[40,449,204],{}," 10:22).",[21,452,453],{},"Notice that the Zoro Parallel's TCGPlayer low price ($695.01) is above its market price ($619.64). The market price reflects recent sales, and the low price is the cheapest current listing. When the low price is above the market price, sellers are asking more than buyers recently paid. That's the kind of signal a tracker should surface.",[282,455,457],{"id":456},"prerequisites","Prerequisites",[112,459,460,474,480,490],{},[115,461,462,465,466,469,470,473],{},[26,463,464],{},"Node.js 18 or newer."," We use the built-in ",[40,467,468],{},"fetch"," and top-level ",[40,471,472],{},"await"," in ES modules. You don't need to install any packages.",[115,475,476,479],{},[26,477,478],{},"A BerryWallet API key"," (covered in Step 1).",[115,481,482,485,486,489],{},[26,483,484],{},"Basic JavaScript."," If you can read an ",[40,487,488],{},"async"," function, you can follow along.",[115,491,492,128,495,498],{},[26,493,494],{},"Optional:",[40,496,497],{},"curl"," for the first test request, and a machine that can run cron (any Linux box, a Mac, or a small VPS).",[21,500,501],{},"Create the project:",[503,504,509],"pre",{"className":505,"code":506,"language":507,"meta":508,"style":508},"language-bash shiki shiki-themes github-light github-dark","mkdir op-price-tracker && cd op-price-tracker\nnpm init -y\nnpm pkg set type=module\n","bash","",[40,510,511,535,547],{"__ignoreMap":508},[512,513,516,520,524,528,532],"span",{"class":514,"line":515},"line",1,[512,517,519],{"class":518},"sScJk","mkdir",[512,521,523],{"class":522},"sZZnC"," op-price-tracker",[512,525,527],{"class":526},"sVt8B"," && ",[512,529,531],{"class":530},"sj4cs","cd",[512,533,534],{"class":522}," op-price-tracker\n",[512,536,538,541,544],{"class":514,"line":537},2,[512,539,540],{"class":518},"npm",[512,542,543],{"class":522}," init",[512,545,546],{"class":530}," -y\n",[512,548,550,552,555,558],{"class":514,"line":549},3,[512,551,540],{"class":518},[512,553,554],{"class":522}," pkg",[512,556,557],{"class":522}," set",[512,559,560],{"class":522}," type=module\n",[21,562,563,564,567,568,469,571,573],{},"Setting ",[40,565,566],{},"\"type\": \"module\""," lets you use ",[40,569,570],{},"import",[40,572,472],{}," without a build step.",[282,575,577],{"id":576},"step-1-get-your-api-key","Step 1: Get Your API Key",[21,579,580,581,584,585,588,589,593,594,598],{},"BerryWallet is the One Piece side of the PokéWallet API. It uses the same host and the same key, with every One Piece route under ",[40,582,583],{},"\u002Fop\u002F",". Sign in at ",[72,586,587],{"href":587},"\u002Fauth\u002Flogin",", then create a key from your ",[72,590,592],{"href":591},"\u002Fdashboard","dashboard",". Plan tiers and request quotas are listed on the ",[72,595,597],{"href":596},"\u002Fpricing","pricing page",". There's a free tier, and that's enough for a personal watchlist.",[21,600,601],{},"Every request authenticates with one header:",[503,603,608],{"className":604,"code":606,"language":607},[605],"language-text","X-API-Key: your_key_here\n","text",[40,609,606],{"__ignoreMap":508},[21,611,612,613,617],{},"The ",[72,614,616],{"href":615},"\u002Fberrywallet-docs#authentication","authentication section of the BerryWallet docs"," covers the details. Two rules for this tutorial:",[112,619,620,626],{},[115,621,622,625],{},[26,623,624],{},"Keep the key out of the code."," Put it in an environment variable, so it never ends up in a commit.",[115,627,628,631],{},[26,629,630],{},"Don't call the API from a browser."," Anyone who opens dev tools can read a key shipped to the front end. This tracker runs on a server or your own machine, which is where the key belongs.",[503,633,635],{"className":505,"code":634,"language":507,"meta":508,"style":508},"export BERRYWALLET_API_KEY=\"your_key_here\"\n",[40,636,637],{"__ignoreMap":508},[512,638,639,643,646,649],{"class":514,"line":515},[512,640,642],{"class":641},"szBVR","export",[512,644,645],{"class":526}," BERRYWALLET_API_KEY",[512,647,648],{"class":641},"=",[512,650,651],{"class":522},"\"your_key_here\"\n",[282,653,655],{"id":654},"step-2-the-first-request","Step 2: The First Request",[21,657,658,659,661],{},"Before writing any JavaScript, check that your key works with ",[40,660,497],{},":",[503,663,665],{"className":505,"code":664,"language":507,"meta":508,"style":508},"curl -s -H \"X-API-Key: $BERRYWALLET_API_KEY\" \\\n  \"https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fsearch?q=luffy&limit=2\"\n",[40,666,667,689],{"__ignoreMap":508},[512,668,669,671,674,677,680,683,686],{"class":514,"line":515},[512,670,497],{"class":518},[512,672,673],{"class":530}," -s",[512,675,676],{"class":530}," -H",[512,678,679],{"class":522}," \"X-API-Key: ",[512,681,682],{"class":526},"$BERRYWALLET_API_KEY",[512,684,685],{"class":522},"\"",[512,687,688],{"class":530}," \\\n",[512,690,691],{"class":514,"line":537},[512,692,693],{"class":522},"  \"https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fsearch?q=luffy&limit=2\"\n",[21,695,696],{},"Here is the real response (trimmed: long IDs shortened, a few fields removed):",[503,698,702],{"className":699,"code":700,"language":701,"meta":508,"style":508},"language-json shiki shiki-themes github-light github-dark","{\n  \"success\": true,\n  \"data\": [\n    {\n      \"id\": \"op_7a01fd97…046f\",\n      \"card_number\": \"EB02-010\",\n      \"name\": \"Monkey.D.Luffy (010) (Dodgers x ONE PIECE)\",\n      \"sub_type_name\": \"Foil\",\n      \"rarity\": \"L\",\n      \"card_type\": \"Leader\",\n      \"tcgplayer\": {\n        \"url\": \"https:\u002F\u002Fwww.tcgplayer.com\u002Fproduct\u002F641620\",\n        \"prices\": {\n          \"low_price\": 1599.95,\n          \"mid_price\": 2432.5,\n          \"high_price\": 16999,\n          \"market_price\": 1534.62,\n          \"updated_at\": \"2026-10-04T11:49:43.703577\"\n        }\n      },\n      \"cardmarket\": null\n    }\n  ],\n  \"total\": 250,\n  \"page\": 1,\n  \"limit\": 2\n}\n","json",[40,703,704,709,723,731,737,750,763,776,789,802,815,824,837,845,858,871,884,897,908,914,920,931,937,943,956,969,980],{"__ignoreMap":508},[512,705,706],{"class":514,"line":515},[512,707,708],{"class":526},"{\n",[512,710,711,714,717,720],{"class":514,"line":537},[512,712,713],{"class":530},"  \"success\"",[512,715,716],{"class":526},": ",[512,718,719],{"class":530},"true",[512,721,722],{"class":526},",\n",[512,724,725,728],{"class":514,"line":549},[512,726,727],{"class":530},"  \"data\"",[512,729,730],{"class":526},": [\n",[512,732,734],{"class":514,"line":733},4,[512,735,736],{"class":526},"    {\n",[512,738,740,743,745,748],{"class":514,"line":739},5,[512,741,742],{"class":530},"      \"id\"",[512,744,716],{"class":526},[512,746,747],{"class":522},"\"op_7a01fd97…046f\"",[512,749,722],{"class":526},[512,751,753,756,758,761],{"class":514,"line":752},6,[512,754,755],{"class":530},"      \"card_number\"",[512,757,716],{"class":526},[512,759,760],{"class":522},"\"EB02-010\"",[512,762,722],{"class":526},[512,764,766,769,771,774],{"class":514,"line":765},7,[512,767,768],{"class":530},"      \"name\"",[512,770,716],{"class":526},[512,772,773],{"class":522},"\"Monkey.D.Luffy (010) (Dodgers x ONE PIECE)\"",[512,775,722],{"class":526},[512,777,779,782,784,787],{"class":514,"line":778},8,[512,780,781],{"class":530},"      \"sub_type_name\"",[512,783,716],{"class":526},[512,785,786],{"class":522},"\"Foil\"",[512,788,722],{"class":526},[512,790,792,795,797,800],{"class":514,"line":791},9,[512,793,794],{"class":530},"      \"rarity\"",[512,796,716],{"class":526},[512,798,799],{"class":522},"\"L\"",[512,801,722],{"class":526},[512,803,805,808,810,813],{"class":514,"line":804},10,[512,806,807],{"class":530},"      \"card_type\"",[512,809,716],{"class":526},[512,811,812],{"class":522},"\"Leader\"",[512,814,722],{"class":526},[512,816,818,821],{"class":514,"line":817},11,[512,819,820],{"class":530},"      \"tcgplayer\"",[512,822,823],{"class":526},": {\n",[512,825,827,830,832,835],{"class":514,"line":826},12,[512,828,829],{"class":530},"        \"url\"",[512,831,716],{"class":526},[512,833,834],{"class":522},"\"https:\u002F\u002Fwww.tcgplayer.com\u002Fproduct\u002F641620\"",[512,836,722],{"class":526},[512,838,840,843],{"class":514,"line":839},13,[512,841,842],{"class":530},"        \"prices\"",[512,844,823],{"class":526},[512,846,848,851,853,856],{"class":514,"line":847},14,[512,849,850],{"class":530},"          \"low_price\"",[512,852,716],{"class":526},[512,854,855],{"class":530},"1599.95",[512,857,722],{"class":526},[512,859,861,864,866,869],{"class":514,"line":860},15,[512,862,863],{"class":530},"          \"mid_price\"",[512,865,716],{"class":526},[512,867,868],{"class":530},"2432.5",[512,870,722],{"class":526},[512,872,874,877,879,882],{"class":514,"line":873},16,[512,875,876],{"class":530},"          \"high_price\"",[512,878,716],{"class":526},[512,880,881],{"class":530},"16999",[512,883,722],{"class":526},[512,885,887,890,892,895],{"class":514,"line":886},17,[512,888,889],{"class":530},"          \"market_price\"",[512,891,716],{"class":526},[512,893,894],{"class":530},"1534.62",[512,896,722],{"class":526},[512,898,900,903,905],{"class":514,"line":899},18,[512,901,902],{"class":530},"          \"updated_at\"",[512,904,716],{"class":526},[512,906,907],{"class":522},"\"2026-10-04T11:49:43.703577\"\n",[512,909,911],{"class":514,"line":910},19,[512,912,913],{"class":526},"        }\n",[512,915,917],{"class":514,"line":916},20,[512,918,919],{"class":526},"      },\n",[512,921,923,926,928],{"class":514,"line":922},21,[512,924,925],{"class":530},"      \"cardmarket\"",[512,927,716],{"class":526},[512,929,930],{"class":530},"null\n",[512,932,934],{"class":514,"line":933},22,[512,935,936],{"class":526},"    }\n",[512,938,940],{"class":514,"line":939},23,[512,941,942],{"class":526},"  ],\n",[512,944,946,949,951,954],{"class":514,"line":945},24,[512,947,948],{"class":530},"  \"total\"",[512,950,716],{"class":526},[512,952,953],{"class":530},"250",[512,955,722],{"class":526},[512,957,959,962,964,967],{"class":514,"line":958},25,[512,960,961],{"class":530},"  \"page\"",[512,963,716],{"class":526},[512,965,966],{"class":530},"1",[512,968,722],{"class":526},[512,970,972,975,977],{"class":514,"line":971},26,[512,973,974],{"class":530},"  \"limit\"",[512,976,716],{"class":526},[512,978,979],{"class":530},"2\n",[512,981,983],{"class":514,"line":982},27,[512,984,985],{"class":526},"}\n",[21,987,988],{},"This one response shows three of the problems the tracker has to handle:",[112,990,991,1005,1017],{},[115,992,993,998,999,1001,1002,1004],{},[26,994,995,997],{},[40,996,127],{}," is not unique."," The second result, which we trimmed out, is also ",[40,1000,131],{},": the Sound Loader Luffy at $30.99. The ",[40,1003,123],{}," is the only field that identifies a single product.",[115,1006,1007,1016],{},[26,1008,1009,1012,1013,105],{},[40,1010,1011],{},"cardmarket"," can be ",[40,1014,1015],{},"null"," Many promos and US-only products aren't listed on CardMarket.",[115,1018,1019,1024],{},[26,1020,1021,105],{},[40,1022,1023],{},"total: 250"," A broad query like \"luffy\" matches hundreds of rows. Search is for finding cards, not for watching them.",[21,1026,1027],{},"The same request in Node:",[503,1029,1033],{"className":1030,"code":1031,"language":1032,"meta":508,"style":508},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F first.js\nconst res = await fetch('https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fsearch?q=luffy&limit=2', {\n  headers: { 'X-API-Key': process.env.BERRYWALLET_API_KEY },\n});\nconst body = await res.json();\n\nfor (const card of body.data) {\n  const tcg = card.tcgplayer?.prices;\n  console.log(\n    card.id.slice(0, 12),\n    card.card_number,\n    card.sub_type_name.padEnd(6),\n    `$${tcg?.market_price ?? '—'}`,\n    card.name,\n  );\n}\n","js",[40,1034,1035,1041,1067,1084,1089,1108,1114,1133,1146,1157,1179,1184,1199,1220,1225,1230],{"__ignoreMap":508},[512,1036,1037],{"class":514,"line":515},[512,1038,1040],{"class":1039},"sJ8bj","\u002F\u002F first.js\n",[512,1042,1043,1046,1049,1052,1055,1058,1061,1064],{"class":514,"line":537},[512,1044,1045],{"class":641},"const",[512,1047,1048],{"class":530}," res",[512,1050,1051],{"class":641}," =",[512,1053,1054],{"class":641}," await",[512,1056,1057],{"class":518}," fetch",[512,1059,1060],{"class":526},"(",[512,1062,1063],{"class":522},"'https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fsearch?q=luffy&limit=2'",[512,1065,1066],{"class":526},", {\n",[512,1068,1069,1072,1075,1078,1081],{"class":514,"line":549},[512,1070,1071],{"class":526},"  headers: { ",[512,1073,1074],{"class":522},"'X-API-Key'",[512,1076,1077],{"class":526},": process.env.",[512,1079,1080],{"class":530},"BERRYWALLET_API_KEY",[512,1082,1083],{"class":526}," },\n",[512,1085,1086],{"class":514,"line":733},[512,1087,1088],{"class":526},"});\n",[512,1090,1091,1093,1096,1098,1100,1103,1105],{"class":514,"line":739},[512,1092,1045],{"class":641},[512,1094,1095],{"class":530}," body",[512,1097,1051],{"class":641},[512,1099,1054],{"class":641},[512,1101,1102],{"class":526}," res.",[512,1104,701],{"class":518},[512,1106,1107],{"class":526},"();\n",[512,1109,1110],{"class":514,"line":752},[512,1111,1113],{"emptyLinePlaceholder":1112},true,"\n",[512,1115,1116,1119,1122,1124,1127,1130],{"class":514,"line":765},[512,1117,1118],{"class":641},"for",[512,1120,1121],{"class":526}," (",[512,1123,1045],{"class":641},[512,1125,1126],{"class":530}," card",[512,1128,1129],{"class":641}," of",[512,1131,1132],{"class":526}," body.data) {\n",[512,1134,1135,1138,1141,1143],{"class":514,"line":778},[512,1136,1137],{"class":641},"  const",[512,1139,1140],{"class":530}," tcg",[512,1142,1051],{"class":641},[512,1144,1145],{"class":526}," card.tcgplayer?.prices;\n",[512,1147,1148,1151,1154],{"class":514,"line":791},[512,1149,1150],{"class":526},"  console.",[512,1152,1153],{"class":518},"log",[512,1155,1156],{"class":526},"(\n",[512,1158,1159,1162,1165,1167,1170,1173,1176],{"class":514,"line":804},[512,1160,1161],{"class":526},"    card.id.",[512,1163,1164],{"class":518},"slice",[512,1166,1060],{"class":526},[512,1168,1169],{"class":530},"0",[512,1171,1172],{"class":526},", ",[512,1174,1175],{"class":530},"12",[512,1177,1178],{"class":526},"),\n",[512,1180,1181],{"class":514,"line":817},[512,1182,1183],{"class":526},"    card.card_number,\n",[512,1185,1186,1189,1192,1194,1197],{"class":514,"line":826},[512,1187,1188],{"class":526},"    card.sub_type_name.",[512,1190,1191],{"class":518},"padEnd",[512,1193,1060],{"class":526},[512,1195,1196],{"class":530},"6",[512,1198,1178],{"class":526},[512,1200,1201,1204,1207,1210,1212,1215,1218],{"class":514,"line":839},[512,1202,1203],{"class":522},"    `$${",[512,1205,1206],{"class":526},"tcg",[512,1208,1209],{"class":522},"?.",[512,1211,180],{"class":526},[512,1213,1214],{"class":641}," ??",[512,1216,1217],{"class":522}," '—'}`",[512,1219,722],{"class":526},[512,1221,1222],{"class":514,"line":847},[512,1223,1224],{"class":526},"    card.name,\n",[512,1226,1227],{"class":514,"line":860},[512,1228,1229],{"class":526},"  );\n",[512,1231,1232],{"class":514,"line":873},[512,1233,985],{"class":526},[503,1235,1237],{"className":505,"code":1236,"language":507,"meta":508,"style":508},"node first.js\n# op_7a01fd975 EB02-010 Foil   $1534.62 Monkey.D.Luffy (010) (Dodgers x ONE PIECE)\n# op_e1cc8f79c EB02-010 Normal $30.99 Monkey.D.Luffy (Sound Loader - Luffy Edition 2025)\n",[40,1238,1239,1247,1252],{"__ignoreMap":508},[512,1240,1241,1244],{"class":514,"line":515},[512,1242,1243],{"class":518},"node",[512,1245,1246],{"class":522}," first.js\n",[512,1248,1249],{"class":514,"line":537},[512,1250,1251],{"class":1039},"# op_7a01fd975 EB02-010 Foil   $1534.62 Monkey.D.Luffy (010) (Dodgers x ONE PIECE)\n",[512,1253,1254],{"class":514,"line":549},[512,1255,1256],{"class":1039},"# op_e1cc8f79c EB02-010 Normal $30.99 Monkey.D.Luffy (Sound Loader - Luffy Edition 2025)\n",[21,1258,1259,1260,661],{},"Now look at the endpoint the tracker actually relies on, ",[72,1261,1263],{"href":1262},"\u002Fberrywallet-docs#endpoint-op-prices",[40,1264,1265],{},"\u002Fop\u002Fprices",[503,1267,1269],{"className":505,"code":1268,"language":507,"meta":508,"style":508},"curl -s -H \"X-API-Key: $BERRYWALLET_API_KEY\" \\\n  \"https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fprices?set_code=OP01&limit=2\"\n",[40,1270,1271,1287],{"__ignoreMap":508},[512,1272,1273,1275,1277,1279,1281,1283,1285],{"class":514,"line":515},[512,1274,497],{"class":518},[512,1276,673],{"class":530},[512,1278,676],{"class":530},[512,1280,679],{"class":522},[512,1282,682],{"class":526},[512,1284,685],{"class":522},[512,1286,688],{"class":530},[512,1288,1289],{"class":514,"line":537},[512,1290,1291],{"class":522},"  \"https:\u002F\u002Fapi.pokewallet.io\u002Fop\u002Fprices?set_code=OP01&limit=2\"\n",[503,1293,1295],{"className":699,"code":1294,"language":701,"meta":508,"style":508},"{\n  \"success\": true,\n  \"group_id\": \"3188\",\n  \"data\": [\n    {\n      \"id\": \"op_f80044ea…b5cd\",\n      \"card_number\": \"OP01-001\",\n      \"name\": \"Roronoa Zoro (001) (Parallel)\",\n      \"variant\": \"Foil\",\n      \"tcgplayer\": {\n        \"low_price\": 695.01, \"mid_price\": 772.5, \"high_price\": 3000,\n        \"market_price\": 619.64, \"direct_low_price\": null,\n        \"updated_at\": \"2026-10-04T11:49:43.703577\"\n      },\n      \"cardmarket\": {\n        \"avg\": 547.2, \"low\": 500, \"trend\": 610.16,\n        \"avg1\": 770, \"avg7\": 630.83, \"avg30\": 613.93,\n        \"updated_at\": \"2026-10-04T10:22:10.294748\"\n      }\n    }\n  ],\n  \"total\": 159\n}\n",[40,1296,1297,1301,1311,1323,1329,1333,1344,1355,1366,1377,1383,1415,1436,1445,1449,1455,1487,1519,1528,1533,1537,1541,1550],{"__ignoreMap":508},[512,1298,1299],{"class":514,"line":515},[512,1300,708],{"class":526},[512,1302,1303,1305,1307,1309],{"class":514,"line":537},[512,1304,713],{"class":530},[512,1306,716],{"class":526},[512,1308,719],{"class":530},[512,1310,722],{"class":526},[512,1312,1313,1316,1318,1321],{"class":514,"line":549},[512,1314,1315],{"class":530},"  \"group_id\"",[512,1317,716],{"class":526},[512,1319,1320],{"class":522},"\"3188\"",[512,1322,722],{"class":526},[512,1324,1325,1327],{"class":514,"line":733},[512,1326,727],{"class":530},[512,1328,730],{"class":526},[512,1330,1331],{"class":514,"line":739},[512,1332,736],{"class":526},[512,1334,1335,1337,1339,1342],{"class":514,"line":752},[512,1336,742],{"class":530},[512,1338,716],{"class":526},[512,1340,1341],{"class":522},"\"op_f80044ea…b5cd\"",[512,1343,722],{"class":526},[512,1345,1346,1348,1350,1353],{"class":514,"line":765},[512,1347,755],{"class":530},[512,1349,716],{"class":526},[512,1351,1352],{"class":522},"\"OP01-001\"",[512,1354,722],{"class":526},[512,1356,1357,1359,1361,1364],{"class":514,"line":778},[512,1358,768],{"class":530},[512,1360,716],{"class":526},[512,1362,1363],{"class":522},"\"Roronoa Zoro (001) (Parallel)\"",[512,1365,722],{"class":526},[512,1367,1368,1371,1373,1375],{"class":514,"line":791},[512,1369,1370],{"class":530},"      \"variant\"",[512,1372,716],{"class":526},[512,1374,786],{"class":522},[512,1376,722],{"class":526},[512,1378,1379,1381],{"class":514,"line":804},[512,1380,820],{"class":530},[512,1382,823],{"class":526},[512,1384,1385,1388,1390,1393,1395,1398,1400,1403,1405,1408,1410,1413],{"class":514,"line":817},[512,1386,1387],{"class":530},"        \"low_price\"",[512,1389,716],{"class":526},[512,1391,1392],{"class":530},"695.01",[512,1394,1172],{"class":526},[512,1396,1397],{"class":530},"\"mid_price\"",[512,1399,716],{"class":526},[512,1401,1402],{"class":530},"772.5",[512,1404,1172],{"class":526},[512,1406,1407],{"class":530},"\"high_price\"",[512,1409,716],{"class":526},[512,1411,1412],{"class":530},"3000",[512,1414,722],{"class":526},[512,1416,1417,1420,1422,1425,1427,1430,1432,1434],{"class":514,"line":826},[512,1418,1419],{"class":530},"        \"market_price\"",[512,1421,716],{"class":526},[512,1423,1424],{"class":530},"619.64",[512,1426,1172],{"class":526},[512,1428,1429],{"class":530},"\"direct_low_price\"",[512,1431,716],{"class":526},[512,1433,1015],{"class":530},[512,1435,722],{"class":526},[512,1437,1438,1441,1443],{"class":514,"line":839},[512,1439,1440],{"class":530},"        \"updated_at\"",[512,1442,716],{"class":526},[512,1444,907],{"class":522},[512,1446,1447],{"class":514,"line":847},[512,1448,919],{"class":526},[512,1450,1451,1453],{"class":514,"line":860},[512,1452,925],{"class":530},[512,1454,823],{"class":526},[512,1456,1457,1460,1462,1465,1467,1470,1472,1475,1477,1480,1482,1485],{"class":514,"line":873},[512,1458,1459],{"class":530},"        \"avg\"",[512,1461,716],{"class":526},[512,1463,1464],{"class":530},"547.2",[512,1466,1172],{"class":526},[512,1468,1469],{"class":530},"\"low\"",[512,1471,716],{"class":526},[512,1473,1474],{"class":530},"500",[512,1476,1172],{"class":526},[512,1478,1479],{"class":530},"\"trend\"",[512,1481,716],{"class":526},[512,1483,1484],{"class":530},"610.16",[512,1486,722],{"class":526},[512,1488,1489,1492,1494,1497,1499,1502,1504,1507,1509,1512,1514,1517],{"class":514,"line":886},[512,1490,1491],{"class":530},"        \"avg1\"",[512,1493,716],{"class":526},[512,1495,1496],{"class":530},"770",[512,1498,1172],{"class":526},[512,1500,1501],{"class":530},"\"avg7\"",[512,1503,716],{"class":526},[512,1505,1506],{"class":530},"630.83",[512,1508,1172],{"class":526},[512,1510,1511],{"class":530},"\"avg30\"",[512,1513,716],{"class":526},[512,1515,1516],{"class":530},"613.93",[512,1518,722],{"class":526},[512,1520,1521,1523,1525],{"class":514,"line":899},[512,1522,1440],{"class":530},[512,1524,716],{"class":526},[512,1526,1527],{"class":522},"\"2026-10-04T10:22:10.294748\"\n",[512,1529,1530],{"class":514,"line":910},[512,1531,1532],{"class":526},"      }\n",[512,1534,1535],{"class":514,"line":916},[512,1536,936],{"class":526},[512,1538,1539],{"class":514,"line":922},[512,1540,942],{"class":526},[512,1542,1543,1545,1547],{"class":514,"line":933},[512,1544,948],{"class":530},[512,1546,716],{"class":526},[512,1548,1549],{"class":530},"159\n",[512,1551,1552],{"class":514,"line":939},[512,1553,985],{"class":526},[21,1555,1556,1557,1560,1561,1563,1564,1567,1568,1571,1572,1575],{},"This response has a different shape from search. Prices sit directly under ",[40,1558,1559],{},"tcgplayer"," and ",[40,1562,1011],{}," instead of under ",[40,1565,1566],{},".prices",", and the variant field is called ",[40,1569,1570],{},"variant"," instead of ",[40,1573,1574],{},"sub_type_name",". The tracker reads from both endpoints, so we'll normalize the two shapes in one function.",[21,1577,1578,1579,1585,1586,1172,1588,1172,1591,1593,1594,1597],{},"To find set codes, call ",[72,1580,1582],{"href":1581},"\u002Fberrywallet-docs#endpoint-op-sets",[40,1583,1584],{},"\u002Fop\u002Fsets?language=en",". It returned 78 sets when we checked, and the codes aren't consistent: ",[40,1587,79],{},[40,1589,1590],{},"OP-PR",[40,1592,83],{},". Codes can contain spaces, so always URL-encode them. The ",[40,1595,1596],{},"URL"," API in the client below does that for you.",[282,1599,1601],{"id":1600},"step-3-building-it-out","Step 3: Building It Out",[107,1603,1605],{"id":1604},"the-client-one-place-for-http-auth-and-retries","The client: one place for HTTP, auth and retries",[503,1607,1609],{"className":1030,"code":1608,"language":1032,"meta":508,"style":508},"\u002F\u002F client.js\nconst BASE_URL = 'https:\u002F\u002Fapi.pokewallet.io';\nconst API_KEY = process.env.BERRYWALLET_API_KEY;\nif (!API_KEY) throw new Error('Set BERRYWALLET_API_KEY before running the tracker');\n\nconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));\n\nexport async function apiGet(path, params = {}, { retries = 4 } = {}) {\n  const url = new URL(path, BASE_URL);\n  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));\n\n  for (let attempt = 0; ; attempt++) {\n    const res = await fetch(url, { headers: { 'X-API-Key': API_KEY } });\n\n    if (res.status === 429 || res.status >= 500) {\n      if (attempt >= retries) throw new Error(`HTTP ${res.status} after ${retries} retries: ${url}`);\n      await sleep(1000 * 2 ** attempt + Math.random() * 250); \u002F\u002F 1s, 2s, 4s, 8s + jitter\n      continue;\n    }\n    if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);\n\n    const body = await res.json();\n    if (!body.success) throw new Error(`API returned success=false for ${url}`);\n    return body;\n  }\n}\n\n\u002F\u002F Collects every row for a paginated endpoint. Stops on a short page or once `total` is reached,\n\u002F\u002F so it terminates even if the endpoint ignores `page`.\nexport async function apiGetAll(path, params = {}, pageSize = 100, maxPages = 20) {\n  const rows = [];\n  for (let page = 1; page \u003C= maxPages; page++) {\n    const body = await apiGet(path, { ...params, limit: pageSize, page });\n    rows.push(...body.data);\n    if (body.data.length \u003C pageSize || rows.length >= (body.total ?? 0)) break;\n  }\n  return rows;\n}\n",[40,1610,1611,1616,1631,1647,1680,1684,1725,1729,1773,1795,1842,1846,1872,1897,1901,1929,1977,2024,2031,2035,2080,2084,2100,2128,2136,2141,2145,2149,2155,2161,2206,2219,2249,2271,2287,2331,2336,2345],{"__ignoreMap":508},[512,1612,1613],{"class":514,"line":515},[512,1614,1615],{"class":1039},"\u002F\u002F client.js\n",[512,1617,1618,1620,1623,1625,1628],{"class":514,"line":537},[512,1619,1045],{"class":641},[512,1621,1622],{"class":530}," BASE_URL",[512,1624,1051],{"class":641},[512,1626,1627],{"class":522}," 'https:\u002F\u002Fapi.pokewallet.io'",[512,1629,1630],{"class":526},";\n",[512,1632,1633,1635,1638,1640,1643,1645],{"class":514,"line":549},[512,1634,1045],{"class":641},[512,1636,1637],{"class":530}," API_KEY",[512,1639,1051],{"class":641},[512,1641,1642],{"class":526}," process.env.",[512,1644,1080],{"class":530},[512,1646,1630],{"class":526},[512,1648,1649,1652,1654,1657,1660,1663,1666,1669,1672,1674,1677],{"class":514,"line":733},[512,1650,1651],{"class":641},"if",[512,1653,1121],{"class":526},[512,1655,1656],{"class":641},"!",[512,1658,1659],{"class":530},"API_KEY",[512,1661,1662],{"class":526},") ",[512,1664,1665],{"class":641},"throw",[512,1667,1668],{"class":641}," new",[512,1670,1671],{"class":518}," Error",[512,1673,1060],{"class":526},[512,1675,1676],{"class":522},"'Set BERRYWALLET_API_KEY before running the tracker'",[512,1678,1679],{"class":526},");\n",[512,1681,1682],{"class":514,"line":739},[512,1683,1113],{"emptyLinePlaceholder":1112},[512,1685,1686,1688,1691,1693,1695,1699,1701,1704,1706,1709,1712,1715,1717,1719,1722],{"class":514,"line":752},[512,1687,1045],{"class":641},[512,1689,1690],{"class":518}," sleep",[512,1692,1051],{"class":641},[512,1694,1121],{"class":526},[512,1696,1698],{"class":1697},"s4XuR","ms",[512,1700,1662],{"class":526},[512,1702,1703],{"class":641},"=>",[512,1705,1668],{"class":641},[512,1707,1708],{"class":530}," Promise",[512,1710,1711],{"class":526},"((",[512,1713,1714],{"class":1697},"r",[512,1716,1662],{"class":526},[512,1718,1703],{"class":641},[512,1720,1721],{"class":518}," setTimeout",[512,1723,1724],{"class":526},"(r, ms));\n",[512,1726,1727],{"class":514,"line":765},[512,1728,1113],{"emptyLinePlaceholder":1112},[512,1730,1731,1733,1736,1739,1742,1744,1747,1749,1752,1754,1757,1760,1762,1765,1768,1770],{"class":514,"line":778},[512,1732,642],{"class":641},[512,1734,1735],{"class":641}," async",[512,1737,1738],{"class":641}," function",[512,1740,1741],{"class":518}," apiGet",[512,1743,1060],{"class":526},[512,1745,1746],{"class":1697},"path",[512,1748,1172],{"class":526},[512,1750,1751],{"class":1697},"params",[512,1753,1051],{"class":641},[512,1755,1756],{"class":526}," {}, { ",[512,1758,1759],{"class":1697},"retries",[512,1761,1051],{"class":641},[512,1763,1764],{"class":530}," 4",[512,1766,1767],{"class":526}," } ",[512,1769,648],{"class":641},[512,1771,1772],{"class":526}," {}) {\n",[512,1774,1775,1777,1780,1782,1784,1787,1790,1793],{"class":514,"line":791},[512,1776,1137],{"class":641},[512,1778,1779],{"class":530}," url",[512,1781,1051],{"class":641},[512,1783,1668],{"class":641},[512,1785,1786],{"class":518}," URL",[512,1788,1789],{"class":526},"(path, ",[512,1791,1792],{"class":530},"BASE_URL",[512,1794,1679],{"class":526},[512,1796,1797,1800,1802,1804,1807,1810,1812,1815,1818,1821,1824,1827,1830,1833,1836,1839],{"class":514,"line":804},[512,1798,1799],{"class":641},"  for",[512,1801,1121],{"class":526},[512,1803,1045],{"class":641},[512,1805,1806],{"class":526}," [",[512,1808,1809],{"class":530},"k",[512,1811,1172],{"class":526},[512,1813,1814],{"class":530},"v",[512,1816,1817],{"class":526},"] ",[512,1819,1820],{"class":641},"of",[512,1822,1823],{"class":526}," Object.",[512,1825,1826],{"class":518},"entries",[512,1828,1829],{"class":526},"(params)) url.searchParams.",[512,1831,1832],{"class":518},"set",[512,1834,1835],{"class":526},"(k, ",[512,1837,1838],{"class":518},"String",[512,1840,1841],{"class":526},"(v));\n",[512,1843,1844],{"class":514,"line":817},[512,1845,1113],{"emptyLinePlaceholder":1112},[512,1847,1848,1850,1852,1855,1858,1860,1863,1866,1869],{"class":514,"line":826},[512,1849,1799],{"class":641},[512,1851,1121],{"class":526},[512,1853,1854],{"class":641},"let",[512,1856,1857],{"class":526}," attempt ",[512,1859,648],{"class":641},[512,1861,1862],{"class":530}," 0",[512,1864,1865],{"class":526},"; ; attempt",[512,1867,1868],{"class":641},"++",[512,1870,1871],{"class":526},") {\n",[512,1873,1874,1877,1879,1881,1883,1885,1888,1890,1892,1894],{"class":514,"line":839},[512,1875,1876],{"class":641},"    const",[512,1878,1048],{"class":530},[512,1880,1051],{"class":641},[512,1882,1054],{"class":641},[512,1884,1057],{"class":518},[512,1886,1887],{"class":526},"(url, { headers: { ",[512,1889,1074],{"class":522},[512,1891,716],{"class":526},[512,1893,1659],{"class":530},[512,1895,1896],{"class":526}," } });\n",[512,1898,1899],{"class":514,"line":847},[512,1900,1113],{"emptyLinePlaceholder":1112},[512,1902,1903,1906,1909,1912,1915,1918,1921,1924,1927],{"class":514,"line":860},[512,1904,1905],{"class":641},"    if",[512,1907,1908],{"class":526}," (res.status ",[512,1910,1911],{"class":641},"===",[512,1913,1914],{"class":530}," 429",[512,1916,1917],{"class":641}," ||",[512,1919,1920],{"class":526}," res.status ",[512,1922,1923],{"class":641},">=",[512,1925,1926],{"class":530}," 500",[512,1928,1871],{"class":526},[512,1930,1931,1934,1937,1939,1942,1944,1946,1948,1950,1953,1956,1958,1961,1964,1966,1969,1972,1975],{"class":514,"line":873},[512,1932,1933],{"class":641},"      if",[512,1935,1936],{"class":526}," (attempt ",[512,1938,1923],{"class":641},[512,1940,1941],{"class":526}," retries) ",[512,1943,1665],{"class":641},[512,1945,1668],{"class":641},[512,1947,1671],{"class":518},[512,1949,1060],{"class":526},[512,1951,1952],{"class":522},"`HTTP ${",[512,1954,1955],{"class":526},"res",[512,1957,105],{"class":522},[512,1959,1960],{"class":526},"status",[512,1962,1963],{"class":522},"} after ${",[512,1965,1759],{"class":526},[512,1967,1968],{"class":522},"} retries: ${",[512,1970,1971],{"class":526},"url",[512,1973,1974],{"class":522},"}`",[512,1976,1679],{"class":526},[512,1978,1979,1982,1984,1986,1989,1992,1995,1998,2000,2003,2006,2009,2012,2015,2018,2021],{"class":514,"line":886},[512,1980,1981],{"class":641},"      await",[512,1983,1690],{"class":518},[512,1985,1060],{"class":526},[512,1987,1988],{"class":530},"1000",[512,1990,1991],{"class":641}," *",[512,1993,1994],{"class":530}," 2",[512,1996,1997],{"class":641}," **",[512,1999,1857],{"class":526},[512,2001,2002],{"class":641},"+",[512,2004,2005],{"class":526}," Math.",[512,2007,2008],{"class":518},"random",[512,2010,2011],{"class":526},"() ",[512,2013,2014],{"class":641},"*",[512,2016,2017],{"class":530}," 250",[512,2019,2020],{"class":526},"); ",[512,2022,2023],{"class":1039},"\u002F\u002F 1s, 2s, 4s, 8s + jitter\n",[512,2025,2026,2029],{"class":514,"line":899},[512,2027,2028],{"class":641},"      continue",[512,2030,1630],{"class":526},[512,2032,2033],{"class":514,"line":910},[512,2034,936],{"class":526},[512,2036,2037,2039,2041,2043,2046,2048,2050,2052,2054,2056,2058,2060,2062,2065,2067,2069,2071,2073,2076,2078],{"class":514,"line":916},[512,2038,1905],{"class":641},[512,2040,1121],{"class":526},[512,2042,1656],{"class":641},[512,2044,2045],{"class":526},"res.ok) ",[512,2047,1665],{"class":641},[512,2049,1668],{"class":641},[512,2051,1671],{"class":518},[512,2053,1060],{"class":526},[512,2055,1952],{"class":522},[512,2057,1955],{"class":526},[512,2059,105],{"class":522},[512,2061,1960],{"class":526},[512,2063,2064],{"class":522},"}: ${",[512,2066,472],{"class":641},[512,2068,1048],{"class":526},[512,2070,105],{"class":522},[512,2072,607],{"class":518},[512,2074,2075],{"class":522},"()",[512,2077,1974],{"class":522},[512,2079,1679],{"class":526},[512,2081,2082],{"class":514,"line":922},[512,2083,1113],{"emptyLinePlaceholder":1112},[512,2085,2086,2088,2090,2092,2094,2096,2098],{"class":514,"line":933},[512,2087,1876],{"class":641},[512,2089,1095],{"class":530},[512,2091,1051],{"class":641},[512,2093,1054],{"class":641},[512,2095,1102],{"class":526},[512,2097,701],{"class":518},[512,2099,1107],{"class":526},[512,2101,2102,2104,2106,2108,2111,2113,2115,2117,2119,2122,2124,2126],{"class":514,"line":939},[512,2103,1905],{"class":641},[512,2105,1121],{"class":526},[512,2107,1656],{"class":641},[512,2109,2110],{"class":526},"body.success) ",[512,2112,1665],{"class":641},[512,2114,1668],{"class":641},[512,2116,1671],{"class":518},[512,2118,1060],{"class":526},[512,2120,2121],{"class":522},"`API returned success=false for ${",[512,2123,1971],{"class":526},[512,2125,1974],{"class":522},[512,2127,1679],{"class":526},[512,2129,2130,2133],{"class":514,"line":945},[512,2131,2132],{"class":641},"    return",[512,2134,2135],{"class":526}," body;\n",[512,2137,2138],{"class":514,"line":958},[512,2139,2140],{"class":526},"  }\n",[512,2142,2143],{"class":514,"line":971},[512,2144,985],{"class":526},[512,2146,2147],{"class":514,"line":982},[512,2148,1113],{"emptyLinePlaceholder":1112},[512,2150,2152],{"class":514,"line":2151},28,[512,2153,2154],{"class":1039},"\u002F\u002F Collects every row for a paginated endpoint. Stops on a short page or once `total` is reached,\n",[512,2156,2158],{"class":514,"line":2157},29,[512,2159,2160],{"class":1039},"\u002F\u002F so it terminates even if the endpoint ignores `page`.\n",[512,2162,2164,2166,2168,2170,2173,2175,2177,2179,2181,2183,2186,2189,2191,2194,2196,2199,2201,2204],{"class":514,"line":2163},30,[512,2165,642],{"class":641},[512,2167,1735],{"class":641},[512,2169,1738],{"class":641},[512,2171,2172],{"class":518}," apiGetAll",[512,2174,1060],{"class":526},[512,2176,1746],{"class":1697},[512,2178,1172],{"class":526},[512,2180,1751],{"class":1697},[512,2182,1051],{"class":641},[512,2184,2185],{"class":526}," {}, ",[512,2187,2188],{"class":1697},"pageSize",[512,2190,1051],{"class":641},[512,2192,2193],{"class":530}," 100",[512,2195,1172],{"class":526},[512,2197,2198],{"class":1697},"maxPages",[512,2200,1051],{"class":641},[512,2202,2203],{"class":530}," 20",[512,2205,1871],{"class":526},[512,2207,2209,2211,2214,2216],{"class":514,"line":2208},31,[512,2210,1137],{"class":641},[512,2212,2213],{"class":530}," rows",[512,2215,1051],{"class":641},[512,2217,2218],{"class":526}," [];\n",[512,2220,2222,2224,2226,2228,2231,2233,2236,2239,2242,2245,2247],{"class":514,"line":2221},32,[512,2223,1799],{"class":641},[512,2225,1121],{"class":526},[512,2227,1854],{"class":641},[512,2229,2230],{"class":526}," page ",[512,2232,648],{"class":641},[512,2234,2235],{"class":530}," 1",[512,2237,2238],{"class":526},"; page ",[512,2240,2241],{"class":641},"\u003C=",[512,2243,2244],{"class":526}," maxPages; page",[512,2246,1868],{"class":641},[512,2248,1871],{"class":526},[512,2250,2252,2254,2256,2258,2260,2262,2265,2268],{"class":514,"line":2251},33,[512,2253,1876],{"class":641},[512,2255,1095],{"class":530},[512,2257,1051],{"class":641},[512,2259,1054],{"class":641},[512,2261,1741],{"class":518},[512,2263,2264],{"class":526},"(path, { ",[512,2266,2267],{"class":641},"...",[512,2269,2270],{"class":526},"params, limit: pageSize, page });\n",[512,2272,2274,2277,2280,2282,2284],{"class":514,"line":2273},34,[512,2275,2276],{"class":526},"    rows.",[512,2278,2279],{"class":518},"push",[512,2281,1060],{"class":526},[512,2283,2267],{"class":641},[512,2285,2286],{"class":526},"body.data);\n",[512,2288,2290,2292,2295,2298,2301,2304,2307,2310,2312,2315,2318,2321,2323,2326,2329],{"class":514,"line":2289},35,[512,2291,1905],{"class":641},[512,2293,2294],{"class":526}," (body.data.",[512,2296,2297],{"class":530},"length",[512,2299,2300],{"class":641}," \u003C",[512,2302,2303],{"class":526}," pageSize ",[512,2305,2306],{"class":641},"||",[512,2308,2309],{"class":526}," rows.",[512,2311,2297],{"class":530},[512,2313,2314],{"class":641}," >=",[512,2316,2317],{"class":526}," (body.total ",[512,2319,2320],{"class":641},"??",[512,2322,1862],{"class":530},[512,2324,2325],{"class":526},")) ",[512,2327,2328],{"class":641},"break",[512,2330,1630],{"class":526},[512,2332,2334],{"class":514,"line":2333},36,[512,2335,2140],{"class":526},[512,2337,2339,2342],{"class":514,"line":2338},37,[512,2340,2341],{"class":641},"  return",[512,2343,2344],{"class":526}," rows;\n",[512,2346,2348],{"class":514,"line":2347},38,[512,2349,985],{"class":526},[21,2351,2352,2353,2356,2357,1560,2360,2363,2364,2367,2368,2370,2371,2373],{},"Pagination: list responses include ",[40,2354,2355],{},"total",", and search and set responses also return ",[40,2358,2359],{},"page",[40,2361,2362],{},"limit",". Check the ",[72,2365,2366],{"href":1262},"prices endpoint docs"," for the maximum ",[40,2369,2362],{}," on your plan. If it's below 100, the loop simply makes more requests. The two stop conditions mean the loop can't run forever, and because the tracker indexes results by ",[40,2372,123],{},", any duplicate rows are harmless.",[107,2375,2377],{"id":2376},"the-watchlist","The watchlist",[21,2379,2380,2381,2383,2384,2387,2388,2391],{},"Each entry needs an ",[40,2382,123],{},". Add ",[40,2385,2386],{},"set_code"," if you know it, which lets the tracker use the cheap per-set fetch. If you don't know the set, add a ",[40,2389,2390],{},"q"," search term instead and the tracker will look the card up through search.",[503,2393,2395],{"className":699,"code":2394,"language":701,"meta":508,"style":508},"[\n  {\n    \"id\": \"op_f80044eaa90595e9caa8d1344cabd70506f20a1eb072a7f233954ef8fbc3f6d3ab0e39d140eddf015c4d23225fd5b1cd\",\n    \"label\": \"Zoro OP01-001 Parallel\",\n    \"set_code\": \"OP01\",\n    \"alertBelow\": 550,\n    \"alertMovePct\": 8\n  },\n  {\n    \"id\": \"op_0f7d2687ec1143b5118027ccb9fb52060de6c5eb63cb4e7bf04f9b7b7666d2ad51f0acbabeff512c13fbd2fb4538de82\",\n    \"label\": \"Zoro OP01-001\",\n    \"set_code\": \"OP01\",\n    \"alertMovePct\": 25\n  },\n  {\n    \"id\": \"op_7a01fd975e2bd571726d00b78a4590439b2607f952480b25627cd12df29f84b11bdadcc4bf33add08bf09ac0d51d01046f\",\n    \"label\": \"Luffy EB02-010 Dodgers\",\n    \"q\": \"luffy dodgers\",\n    \"alertBelow\": 1400,\n    \"alertMovePct\": 10\n  }\n]\n",[40,2396,2397,2402,2407,2419,2431,2443,2455,2465,2470,2474,2485,2496,2506,2515,2519,2523,2534,2545,2557,2568,2577,2581],{"__ignoreMap":508},[512,2398,2399],{"class":514,"line":515},[512,2400,2401],{"class":526},"[\n",[512,2403,2404],{"class":514,"line":537},[512,2405,2406],{"class":526},"  {\n",[512,2408,2409,2412,2414,2417],{"class":514,"line":549},[512,2410,2411],{"class":530},"    \"id\"",[512,2413,716],{"class":526},[512,2415,2416],{"class":522},"\"op_f80044eaa90595e9caa8d1344cabd70506f20a1eb072a7f233954ef8fbc3f6d3ab0e39d140eddf015c4d23225fd5b1cd\"",[512,2418,722],{"class":526},[512,2420,2421,2424,2426,2429],{"class":514,"line":733},[512,2422,2423],{"class":530},"    \"label\"",[512,2425,716],{"class":526},[512,2427,2428],{"class":522},"\"Zoro OP01-001 Parallel\"",[512,2430,722],{"class":526},[512,2432,2433,2436,2438,2441],{"class":514,"line":739},[512,2434,2435],{"class":530},"    \"set_code\"",[512,2437,716],{"class":526},[512,2439,2440],{"class":522},"\"OP01\"",[512,2442,722],{"class":526},[512,2444,2445,2448,2450,2453],{"class":514,"line":752},[512,2446,2447],{"class":530},"    \"alertBelow\"",[512,2449,716],{"class":526},[512,2451,2452],{"class":530},"550",[512,2454,722],{"class":526},[512,2456,2457,2460,2462],{"class":514,"line":765},[512,2458,2459],{"class":530},"    \"alertMovePct\"",[512,2461,716],{"class":526},[512,2463,2464],{"class":530},"8\n",[512,2466,2467],{"class":514,"line":778},[512,2468,2469],{"class":526},"  },\n",[512,2471,2472],{"class":514,"line":791},[512,2473,2406],{"class":526},[512,2475,2476,2478,2480,2483],{"class":514,"line":804},[512,2477,2411],{"class":530},[512,2479,716],{"class":526},[512,2481,2482],{"class":522},"\"op_0f7d2687ec1143b5118027ccb9fb52060de6c5eb63cb4e7bf04f9b7b7666d2ad51f0acbabeff512c13fbd2fb4538de82\"",[512,2484,722],{"class":526},[512,2486,2487,2489,2491,2494],{"class":514,"line":817},[512,2488,2423],{"class":530},[512,2490,716],{"class":526},[512,2492,2493],{"class":522},"\"Zoro OP01-001\"",[512,2495,722],{"class":526},[512,2497,2498,2500,2502,2504],{"class":514,"line":826},[512,2499,2435],{"class":530},[512,2501,716],{"class":526},[512,2503,2440],{"class":522},[512,2505,722],{"class":526},[512,2507,2508,2510,2512],{"class":514,"line":839},[512,2509,2459],{"class":530},[512,2511,716],{"class":526},[512,2513,2514],{"class":530},"25\n",[512,2516,2517],{"class":514,"line":847},[512,2518,2469],{"class":526},[512,2520,2521],{"class":514,"line":860},[512,2522,2406],{"class":526},[512,2524,2525,2527,2529,2532],{"class":514,"line":873},[512,2526,2411],{"class":530},[512,2528,716],{"class":526},[512,2530,2531],{"class":522},"\"op_7a01fd975e2bd571726d00b78a4590439b2607f952480b25627cd12df29f84b11bdadcc4bf33add08bf09ac0d51d01046f\"",[512,2533,722],{"class":526},[512,2535,2536,2538,2540,2543],{"class":514,"line":886},[512,2537,2423],{"class":530},[512,2539,716],{"class":526},[512,2541,2542],{"class":522},"\"Luffy EB02-010 Dodgers\"",[512,2544,722],{"class":526},[512,2546,2547,2550,2552,2555],{"class":514,"line":899},[512,2548,2549],{"class":530},"    \"q\"",[512,2551,716],{"class":526},[512,2553,2554],{"class":522},"\"luffy dodgers\"",[512,2556,722],{"class":526},[512,2558,2559,2561,2563,2566],{"class":514,"line":910},[512,2560,2447],{"class":530},[512,2562,716],{"class":526},[512,2564,2565],{"class":530},"1400",[512,2567,722],{"class":526},[512,2569,2570,2572,2574],{"class":514,"line":916},[512,2571,2459],{"class":530},[512,2573,716],{"class":526},[512,2575,2576],{"class":530},"10\n",[512,2578,2579],{"class":514,"line":922},[512,2580,2140],{"class":526},[512,2582,2583],{"class":514,"line":933},[512,2584,2585],{"class":526},"]\n",[21,2587,2588],{},"Each card gets its own move threshold. A 25% swing on a $2 card is noise. An 8% swing on a $600 card is about $50, which is enough to act on.",[107,2590,2592],{"id":2591},"the-tracker","The tracker",[503,2594,2596],{"className":1030,"code":2595,"language":1032,"meta":508,"style":508},"\u002F\u002F tracker.js\nimport { readFile, writeFile } from 'node:fs\u002Fpromises';\nimport { apiGetAll } from '.\u002Fclient.js';\n\nconst SPIKE_PCT = 20; \u002F\u002F CardMarket 1-day avg vs 30-day avg\n\nconst watchlist = JSON.parse(await readFile('watchlist.json', 'utf8'));\nconst history = await readFile('history.json', 'utf8').then(JSON.parse).catch(() => []);\n\n\u002F\u002F \u002Fop\u002Fprices nests prices directly; \u002Fop\u002Fsearch nests them under `.prices`. Flatten both.\nfunction normalize(row) {\n  const tcg = row.tcgplayer?.prices ?? row.tcgplayer ?? null;\n  const cm = row.cardmarket?.prices ?? row.cardmarket ?? null;\n  return {\n    id: row.id,\n    name: row.name,\n    variant: row.variant ?? row.sub_type_name,\n    tcgMarket: tcg?.market_price ?? null, \u002F\u002F USD\n    tcgLow: tcg?.low_price ?? null,\n    tcgUpdated: tcg?.updated_at ?? null,\n    cmTrend: cm?.trend ?? null,           \u002F\u002F EUR\n    cmAvg1: cm?.avg1 ?? null,\n    cmAvg30: cm?.avg30 ?? null,\n    cmUpdated: cm?.updated_at ?? null,\n  };\n}\n\nasync function fetchCurrent(items) {\n  const found = new Map();\n\n  \u002F\u002F One paginated call per set, however many cards you watch in it.\n  const sets = [...new Set(items.filter((i) => i.set_code).map((i) => i.set_code))];\n  for (const set_code of sets) {\n    for (const row of await apiGetAll('\u002Fop\u002Fprices', { set_code })) found.set(row.id, normalize(row));\n  }\n\n  \u002F\u002F Cards without a set code: search, then match on id.\n  for (const item of items.filter((i) => !i.set_code && i.q)) {\n    const rows = await apiGetAll('\u002Fop\u002Fsearch', { q: item.q }, 100, 5);\n    const hit = rows.find((r) => r.id === item.id);\n    if (hit) found.set(hit.id, normalize(hit));\n  }\n  return found;\n}\n\nconst pct = (now, before) => ((now - before) \u002F before) * 100;\n\nfunction checkAlerts(item, cur, prev) {\n  const alerts = [];\n  if (item.alertBelow != null && cur.tcgMarket != null && cur.tcgMarket \u003C= item.alertBelow) {\n    alerts.push(`market $${cur.tcgMarket} is at\u002Fbelow target $${item.alertBelow}`);\n  }\n  if (item.alertMovePct != null && prev?.tcgMarket && cur.tcgMarket != null) {\n    const move = pct(cur.tcgMarket, prev.tcgMarket);\n    if (Math.abs(move) >= item.alertMovePct) {\n      alerts.push(`market moved ${move.toFixed(1)}% since ${prev.tcgUpdated} ($${prev.tcgMarket} → $${cur.tcgMarket})`);\n    }\n  }\n  if (cur.cmAvg1 && cur.cmAvg30) {\n    const spike = pct(cur.cmAvg1, cur.cmAvg30);\n    if (Math.abs(spike) >= SPIKE_PCT) alerts.push(`CardMarket 1-day avg ${spike.toFixed(1)}% vs 30-day avg`);\n  }\n  return alerts;\n}\n\nconst current = await fetchCurrent(watchlist);\nconst now = new Date().toISOString();\nconst report = [];\n\nfor (const item of watchlist) {\n  const cur = current.get(item.id);\n  if (!cur) { console.warn(`⚠️  ${item.label}: not found — check id \u002F set_code \u002F q`); continue; }\n\n  const prev = history.filter((h) => h.id === item.id).at(-1);\n  const alerts = checkAlerts(item, cur, prev);\n\n  \u002F\u002F Only store a snapshot when the source data has actually refreshed.\n  if (!prev || prev.tcgUpdated !== cur.tcgUpdated || prev.cmUpdated !== cur.cmUpdated) {\n    history.push({ fetchedAt: now, ...cur });\n  }\n\n  report.push({\n    card: item.label,\n    variant: cur.variant,\n    'TCG $': cur.tcgMarket ?? '—',\n    'TCG low $': cur.tcgLow ?? '—',\n    'CM trend €': cur.cmTrend ?? '—',\n    alerts: alerts.length,\n  });\n  for (const a of alerts) console.log(`🚨 ${item.label}: ${a}`);\n}\n\nconsole.table(report);\nawait writeFile('history.json', JSON.stringify(history, null, 2));\n",[40,2597,2598,2603,2618,2632,2636,2653,2657,2694,2740,2744,2749,2764,2787,2810,2817,2822,2827,2837,2851,2862,2873,2888,2899,2910,2921,2926,2930,2934,2950,2966,2970,2975,3024,3040,3077,3081,3085,3090,3128,3159,3190,3208,3213,3221,3226,3231,3275,3280,3305,3317,3350,3384,3389,3416,3431,3450,3511,3516,3521,3534,3549,3593,3598,3606,3611,3616,3633,3656,3668,3673,3689,3708,3746,3751,3794,3808,3813,3819,3852,3868,3873,3878,3889,3895,3901,3917,3932,3947,3957,3963,4001,4006,4011,4022],{"__ignoreMap":508},[512,2599,2600],{"class":514,"line":515},[512,2601,2602],{"class":1039},"\u002F\u002F tracker.js\n",[512,2604,2605,2607,2610,2613,2616],{"class":514,"line":537},[512,2606,570],{"class":641},[512,2608,2609],{"class":526}," { readFile, writeFile } ",[512,2611,2612],{"class":641},"from",[512,2614,2615],{"class":522}," 'node:fs\u002Fpromises'",[512,2617,1630],{"class":526},[512,2619,2620,2622,2625,2627,2630],{"class":514,"line":549},[512,2621,570],{"class":641},[512,2623,2624],{"class":526}," { apiGetAll } ",[512,2626,2612],{"class":641},[512,2628,2629],{"class":522}," '.\u002Fclient.js'",[512,2631,1630],{"class":526},[512,2633,2634],{"class":514,"line":733},[512,2635,1113],{"emptyLinePlaceholder":1112},[512,2637,2638,2640,2643,2645,2647,2650],{"class":514,"line":739},[512,2639,1045],{"class":641},[512,2641,2642],{"class":530}," SPIKE_PCT",[512,2644,1051],{"class":641},[512,2646,2203],{"class":530},[512,2648,2649],{"class":526},"; ",[512,2651,2652],{"class":1039},"\u002F\u002F CardMarket 1-day avg vs 30-day avg\n",[512,2654,2655],{"class":514,"line":752},[512,2656,1113],{"emptyLinePlaceholder":1112},[512,2658,2659,2661,2664,2666,2669,2671,2674,2676,2678,2681,2683,2686,2688,2691],{"class":514,"line":765},[512,2660,1045],{"class":641},[512,2662,2663],{"class":530}," watchlist",[512,2665,1051],{"class":641},[512,2667,2668],{"class":530}," JSON",[512,2670,105],{"class":526},[512,2672,2673],{"class":518},"parse",[512,2675,1060],{"class":526},[512,2677,472],{"class":641},[512,2679,2680],{"class":518}," readFile",[512,2682,1060],{"class":526},[512,2684,2685],{"class":522},"'watchlist.json'",[512,2687,1172],{"class":526},[512,2689,2690],{"class":522},"'utf8'",[512,2692,2693],{"class":526},"));\n",[512,2695,2696,2698,2701,2703,2705,2707,2709,2712,2714,2716,2718,2721,2723,2726,2729,2732,2735,2737],{"class":514,"line":778},[512,2697,1045],{"class":641},[512,2699,2700],{"class":530}," history",[512,2702,1051],{"class":641},[512,2704,1054],{"class":641},[512,2706,2680],{"class":518},[512,2708,1060],{"class":526},[512,2710,2711],{"class":522},"'history.json'",[512,2713,1172],{"class":526},[512,2715,2690],{"class":522},[512,2717,329],{"class":526},[512,2719,2720],{"class":518},"then",[512,2722,1060],{"class":526},[512,2724,2725],{"class":530},"JSON",[512,2727,2728],{"class":526},".parse).",[512,2730,2731],{"class":518},"catch",[512,2733,2734],{"class":526},"(() ",[512,2736,1703],{"class":641},[512,2738,2739],{"class":526}," []);\n",[512,2741,2742],{"class":514,"line":791},[512,2743,1113],{"emptyLinePlaceholder":1112},[512,2745,2746],{"class":514,"line":804},[512,2747,2748],{"class":1039},"\u002F\u002F \u002Fop\u002Fprices nests prices directly; \u002Fop\u002Fsearch nests them under `.prices`. Flatten both.\n",[512,2750,2751,2754,2757,2759,2762],{"class":514,"line":817},[512,2752,2753],{"class":641},"function",[512,2755,2756],{"class":518}," normalize",[512,2758,1060],{"class":526},[512,2760,2761],{"class":1697},"row",[512,2763,1871],{"class":526},[512,2765,2766,2768,2770,2772,2775,2777,2780,2782,2785],{"class":514,"line":826},[512,2767,1137],{"class":641},[512,2769,1140],{"class":530},[512,2771,1051],{"class":641},[512,2773,2774],{"class":526}," row.tcgplayer?.prices ",[512,2776,2320],{"class":641},[512,2778,2779],{"class":526}," row.tcgplayer ",[512,2781,2320],{"class":641},[512,2783,2784],{"class":530}," null",[512,2786,1630],{"class":526},[512,2788,2789,2791,2794,2796,2799,2801,2804,2806,2808],{"class":514,"line":839},[512,2790,1137],{"class":641},[512,2792,2793],{"class":530}," cm",[512,2795,1051],{"class":641},[512,2797,2798],{"class":526}," row.cardmarket?.prices ",[512,2800,2320],{"class":641},[512,2802,2803],{"class":526}," row.cardmarket ",[512,2805,2320],{"class":641},[512,2807,2784],{"class":530},[512,2809,1630],{"class":526},[512,2811,2812,2814],{"class":514,"line":847},[512,2813,2341],{"class":641},[512,2815,2816],{"class":526}," {\n",[512,2818,2819],{"class":514,"line":860},[512,2820,2821],{"class":526},"    id: row.id,\n",[512,2823,2824],{"class":514,"line":873},[512,2825,2826],{"class":526},"    name: row.name,\n",[512,2828,2829,2832,2834],{"class":514,"line":886},[512,2830,2831],{"class":526},"    variant: row.variant ",[512,2833,2320],{"class":641},[512,2835,2836],{"class":526}," row.sub_type_name,\n",[512,2838,2839,2842,2844,2846,2848],{"class":514,"line":899},[512,2840,2841],{"class":526},"    tcgMarket: tcg?.market_price ",[512,2843,2320],{"class":641},[512,2845,2784],{"class":530},[512,2847,1172],{"class":526},[512,2849,2850],{"class":1039},"\u002F\u002F USD\n",[512,2852,2853,2856,2858,2860],{"class":514,"line":910},[512,2854,2855],{"class":526},"    tcgLow: tcg?.low_price ",[512,2857,2320],{"class":641},[512,2859,2784],{"class":530},[512,2861,722],{"class":526},[512,2863,2864,2867,2869,2871],{"class":514,"line":916},[512,2865,2866],{"class":526},"    tcgUpdated: tcg?.updated_at ",[512,2868,2320],{"class":641},[512,2870,2784],{"class":530},[512,2872,722],{"class":526},[512,2874,2875,2878,2880,2882,2885],{"class":514,"line":922},[512,2876,2877],{"class":526},"    cmTrend: cm?.trend ",[512,2879,2320],{"class":641},[512,2881,2784],{"class":530},[512,2883,2884],{"class":526},",           ",[512,2886,2887],{"class":1039},"\u002F\u002F EUR\n",[512,2889,2890,2893,2895,2897],{"class":514,"line":933},[512,2891,2892],{"class":526},"    cmAvg1: cm?.avg1 ",[512,2894,2320],{"class":641},[512,2896,2784],{"class":530},[512,2898,722],{"class":526},[512,2900,2901,2904,2906,2908],{"class":514,"line":939},[512,2902,2903],{"class":526},"    cmAvg30: cm?.avg30 ",[512,2905,2320],{"class":641},[512,2907,2784],{"class":530},[512,2909,722],{"class":526},[512,2911,2912,2915,2917,2919],{"class":514,"line":945},[512,2913,2914],{"class":526},"    cmUpdated: cm?.updated_at ",[512,2916,2320],{"class":641},[512,2918,2784],{"class":530},[512,2920,722],{"class":526},[512,2922,2923],{"class":514,"line":958},[512,2924,2925],{"class":526},"  };\n",[512,2927,2928],{"class":514,"line":971},[512,2929,985],{"class":526},[512,2931,2932],{"class":514,"line":982},[512,2933,1113],{"emptyLinePlaceholder":1112},[512,2935,2936,2938,2940,2943,2945,2948],{"class":514,"line":2151},[512,2937,488],{"class":641},[512,2939,1738],{"class":641},[512,2941,2942],{"class":518}," fetchCurrent",[512,2944,1060],{"class":526},[512,2946,2947],{"class":1697},"items",[512,2949,1871],{"class":526},[512,2951,2952,2954,2957,2959,2961,2964],{"class":514,"line":2157},[512,2953,1137],{"class":641},[512,2955,2956],{"class":530}," found",[512,2958,1051],{"class":641},[512,2960,1668],{"class":641},[512,2962,2963],{"class":518}," Map",[512,2965,1107],{"class":526},[512,2967,2968],{"class":514,"line":2163},[512,2969,1113],{"emptyLinePlaceholder":1112},[512,2971,2972],{"class":514,"line":2208},[512,2973,2974],{"class":1039},"  \u002F\u002F One paginated call per set, however many cards you watch in it.\n",[512,2976,2977,2979,2982,2984,2986,2989,2992,2995,2998,3000,3003,3005,3007,3010,3013,3015,3017,3019,3021],{"class":514,"line":2221},[512,2978,1137],{"class":641},[512,2980,2981],{"class":530}," sets",[512,2983,1051],{"class":641},[512,2985,1806],{"class":526},[512,2987,2988],{"class":641},"...new",[512,2990,2991],{"class":518}," Set",[512,2993,2994],{"class":526},"(items.",[512,2996,2997],{"class":518},"filter",[512,2999,1711],{"class":526},[512,3001,3002],{"class":1697},"i",[512,3004,1662],{"class":526},[512,3006,1703],{"class":641},[512,3008,3009],{"class":526}," i.set_code).",[512,3011,3012],{"class":518},"map",[512,3014,1711],{"class":526},[512,3016,3002],{"class":1697},[512,3018,1662],{"class":526},[512,3020,1703],{"class":641},[512,3022,3023],{"class":526}," i.set_code))];\n",[512,3025,3026,3028,3030,3032,3035,3037],{"class":514,"line":2251},[512,3027,1799],{"class":641},[512,3029,1121],{"class":526},[512,3031,1045],{"class":641},[512,3033,3034],{"class":530}," set_code",[512,3036,1129],{"class":641},[512,3038,3039],{"class":526}," sets) {\n",[512,3041,3042,3045,3047,3049,3052,3054,3056,3058,3060,3063,3066,3068,3071,3074],{"class":514,"line":2273},[512,3043,3044],{"class":641},"    for",[512,3046,1121],{"class":526},[512,3048,1045],{"class":641},[512,3050,3051],{"class":530}," row",[512,3053,1129],{"class":641},[512,3055,1054],{"class":641},[512,3057,2172],{"class":518},[512,3059,1060],{"class":526},[512,3061,3062],{"class":522},"'\u002Fop\u002Fprices'",[512,3064,3065],{"class":526},", { set_code })) found.",[512,3067,1832],{"class":518},[512,3069,3070],{"class":526},"(row.id, ",[512,3072,3073],{"class":518},"normalize",[512,3075,3076],{"class":526},"(row));\n",[512,3078,3079],{"class":514,"line":2289},[512,3080,2140],{"class":526},[512,3082,3083],{"class":514,"line":2333},[512,3084,1113],{"emptyLinePlaceholder":1112},[512,3086,3087],{"class":514,"line":2338},[512,3088,3089],{"class":1039},"  \u002F\u002F Cards without a set code: search, then match on id.\n",[512,3091,3092,3094,3096,3098,3101,3103,3106,3108,3110,3112,3114,3116,3119,3122,3125],{"class":514,"line":2347},[512,3093,1799],{"class":641},[512,3095,1121],{"class":526},[512,3097,1045],{"class":641},[512,3099,3100],{"class":530}," item",[512,3102,1129],{"class":641},[512,3104,3105],{"class":526}," items.",[512,3107,2997],{"class":518},[512,3109,1711],{"class":526},[512,3111,3002],{"class":1697},[512,3113,1662],{"class":526},[512,3115,1703],{"class":641},[512,3117,3118],{"class":641}," !",[512,3120,3121],{"class":526},"i.set_code ",[512,3123,3124],{"class":641},"&&",[512,3126,3127],{"class":526}," i.q)) {\n",[512,3129,3131,3133,3135,3137,3139,3141,3143,3146,3149,3152,3154,3157],{"class":514,"line":3130},39,[512,3132,1876],{"class":641},[512,3134,2213],{"class":530},[512,3136,1051],{"class":641},[512,3138,1054],{"class":641},[512,3140,2172],{"class":518},[512,3142,1060],{"class":526},[512,3144,3145],{"class":522},"'\u002Fop\u002Fsearch'",[512,3147,3148],{"class":526},", { q: item.q }, ",[512,3150,3151],{"class":530},"100",[512,3153,1172],{"class":526},[512,3155,3156],{"class":530},"5",[512,3158,1679],{"class":526},[512,3160,3162,3164,3167,3169,3171,3174,3176,3178,3180,3182,3185,3187],{"class":514,"line":3161},40,[512,3163,1876],{"class":641},[512,3165,3166],{"class":530}," hit",[512,3168,1051],{"class":641},[512,3170,2309],{"class":526},[512,3172,3173],{"class":518},"find",[512,3175,1711],{"class":526},[512,3177,1714],{"class":1697},[512,3179,1662],{"class":526},[512,3181,1703],{"class":641},[512,3183,3184],{"class":526}," r.id ",[512,3186,1911],{"class":641},[512,3188,3189],{"class":526}," item.id);\n",[512,3191,3193,3195,3198,3200,3203,3205],{"class":514,"line":3192},41,[512,3194,1905],{"class":641},[512,3196,3197],{"class":526}," (hit) found.",[512,3199,1832],{"class":518},[512,3201,3202],{"class":526},"(hit.id, ",[512,3204,3073],{"class":518},[512,3206,3207],{"class":526},"(hit));\n",[512,3209,3211],{"class":514,"line":3210},42,[512,3212,2140],{"class":526},[512,3214,3216,3218],{"class":514,"line":3215},43,[512,3217,2341],{"class":641},[512,3219,3220],{"class":526}," found;\n",[512,3222,3224],{"class":514,"line":3223},44,[512,3225,985],{"class":526},[512,3227,3229],{"class":514,"line":3228},45,[512,3230,1113],{"emptyLinePlaceholder":1112},[512,3232,3234,3236,3239,3241,3243,3246,3248,3251,3253,3255,3258,3261,3264,3267,3269,3271,3273],{"class":514,"line":3233},46,[512,3235,1045],{"class":641},[512,3237,3238],{"class":518}," pct",[512,3240,1051],{"class":641},[512,3242,1121],{"class":526},[512,3244,3245],{"class":1697},"now",[512,3247,1172],{"class":526},[512,3249,3250],{"class":1697},"before",[512,3252,1662],{"class":526},[512,3254,1703],{"class":641},[512,3256,3257],{"class":526}," ((now ",[512,3259,3260],{"class":641},"-",[512,3262,3263],{"class":526}," before) ",[512,3265,3266],{"class":641},"\u002F",[512,3268,3263],{"class":526},[512,3270,2014],{"class":641},[512,3272,2193],{"class":530},[512,3274,1630],{"class":526},[512,3276,3278],{"class":514,"line":3277},47,[512,3279,1113],{"emptyLinePlaceholder":1112},[512,3281,3283,3285,3288,3290,3293,3295,3298,3300,3303],{"class":514,"line":3282},48,[512,3284,2753],{"class":641},[512,3286,3287],{"class":518}," checkAlerts",[512,3289,1060],{"class":526},[512,3291,3292],{"class":1697},"item",[512,3294,1172],{"class":526},[512,3296,3297],{"class":1697},"cur",[512,3299,1172],{"class":526},[512,3301,3302],{"class":1697},"prev",[512,3304,1871],{"class":526},[512,3306,3308,3310,3313,3315],{"class":514,"line":3307},49,[512,3309,1137],{"class":641},[512,3311,3312],{"class":530}," alerts",[512,3314,1051],{"class":641},[512,3316,2218],{"class":526},[512,3318,3320,3323,3326,3329,3331,3334,3337,3339,3341,3343,3345,3347],{"class":514,"line":3319},50,[512,3321,3322],{"class":641},"  if",[512,3324,3325],{"class":526}," (item.alertBelow ",[512,3327,3328],{"class":641},"!=",[512,3330,2784],{"class":530},[512,3332,3333],{"class":641}," &&",[512,3335,3336],{"class":526}," cur.tcgMarket ",[512,3338,3328],{"class":641},[512,3340,2784],{"class":530},[512,3342,3333],{"class":641},[512,3344,3336],{"class":526},[512,3346,2241],{"class":641},[512,3348,3349],{"class":526}," item.alertBelow) {\n",[512,3351,3353,3356,3358,3360,3363,3365,3367,3370,3373,3375,3377,3380,3382],{"class":514,"line":3352},51,[512,3354,3355],{"class":526},"    alerts.",[512,3357,2279],{"class":518},[512,3359,1060],{"class":526},[512,3361,3362],{"class":522},"`market $${",[512,3364,3297],{"class":526},[512,3366,105],{"class":522},[512,3368,3369],{"class":526},"tcgMarket",[512,3371,3372],{"class":522},"} is at\u002Fbelow target $${",[512,3374,3292],{"class":526},[512,3376,105],{"class":522},[512,3378,3379],{"class":526},"alertBelow",[512,3381,1974],{"class":522},[512,3383,1679],{"class":526},[512,3385,3387],{"class":514,"line":3386},52,[512,3388,2140],{"class":526},[512,3390,3392,3394,3397,3399,3401,3403,3406,3408,3410,3412,3414],{"class":514,"line":3391},53,[512,3393,3322],{"class":641},[512,3395,3396],{"class":526}," (item.alertMovePct ",[512,3398,3328],{"class":641},[512,3400,2784],{"class":530},[512,3402,3333],{"class":641},[512,3404,3405],{"class":526}," prev?.tcgMarket ",[512,3407,3124],{"class":641},[512,3409,3336],{"class":526},[512,3411,3328],{"class":641},[512,3413,2784],{"class":530},[512,3415,1871],{"class":526},[512,3417,3419,3421,3424,3426,3428],{"class":514,"line":3418},54,[512,3420,1876],{"class":641},[512,3422,3423],{"class":530}," move",[512,3425,1051],{"class":641},[512,3427,3238],{"class":518},[512,3429,3430],{"class":526},"(cur.tcgMarket, prev.tcgMarket);\n",[512,3432,3434,3436,3439,3442,3445,3447],{"class":514,"line":3433},55,[512,3435,1905],{"class":641},[512,3437,3438],{"class":526}," (Math.",[512,3440,3441],{"class":518},"abs",[512,3443,3444],{"class":526},"(move) ",[512,3446,1923],{"class":641},[512,3448,3449],{"class":526}," item.alertMovePct) {\n",[512,3451,3453,3456,3458,3460,3463,3466,3468,3471,3473,3475,3478,3481,3483,3485,3488,3491,3493,3495,3497,3500,3502,3504,3506,3509],{"class":514,"line":3452},56,[512,3454,3455],{"class":526},"      alerts.",[512,3457,2279],{"class":518},[512,3459,1060],{"class":526},[512,3461,3462],{"class":522},"`market moved ${",[512,3464,3465],{"class":526},"move",[512,3467,105],{"class":522},[512,3469,3470],{"class":518},"toFixed",[512,3472,1060],{"class":522},[512,3474,966],{"class":530},[512,3476,3477],{"class":522},")",[512,3479,3480],{"class":522},"}% since ${",[512,3482,3302],{"class":526},[512,3484,105],{"class":522},[512,3486,3487],{"class":526},"tcgUpdated",[512,3489,3490],{"class":522},"} ($${",[512,3492,3302],{"class":526},[512,3494,105],{"class":522},[512,3496,3369],{"class":526},[512,3498,3499],{"class":522},"} → $${",[512,3501,3297],{"class":526},[512,3503,105],{"class":522},[512,3505,3369],{"class":526},[512,3507,3508],{"class":522},"})`",[512,3510,1679],{"class":526},[512,3512,3514],{"class":514,"line":3513},57,[512,3515,936],{"class":526},[512,3517,3519],{"class":514,"line":3518},58,[512,3520,2140],{"class":526},[512,3522,3524,3526,3529,3531],{"class":514,"line":3523},59,[512,3525,3322],{"class":641},[512,3527,3528],{"class":526}," (cur.cmAvg1 ",[512,3530,3124],{"class":641},[512,3532,3533],{"class":526}," cur.cmAvg30) {\n",[512,3535,3537,3539,3542,3544,3546],{"class":514,"line":3536},60,[512,3538,1876],{"class":641},[512,3540,3541],{"class":530}," spike",[512,3543,1051],{"class":641},[512,3545,3238],{"class":518},[512,3547,3548],{"class":526},"(cur.cmAvg1, cur.cmAvg30);\n",[512,3550,3552,3554,3556,3558,3561,3563,3565,3568,3570,3572,3575,3578,3580,3582,3584,3586,3588,3591],{"class":514,"line":3551},61,[512,3553,1905],{"class":641},[512,3555,3438],{"class":526},[512,3557,3441],{"class":518},[512,3559,3560],{"class":526},"(spike) ",[512,3562,1923],{"class":641},[512,3564,2642],{"class":530},[512,3566,3567],{"class":526},") alerts.",[512,3569,2279],{"class":518},[512,3571,1060],{"class":526},[512,3573,3574],{"class":522},"`CardMarket 1-day avg ${",[512,3576,3577],{"class":526},"spike",[512,3579,105],{"class":522},[512,3581,3470],{"class":518},[512,3583,1060],{"class":522},[512,3585,966],{"class":530},[512,3587,3477],{"class":522},[512,3589,3590],{"class":522},"}% vs 30-day avg`",[512,3592,1679],{"class":526},[512,3594,3596],{"class":514,"line":3595},62,[512,3597,2140],{"class":526},[512,3599,3601,3603],{"class":514,"line":3600},63,[512,3602,2341],{"class":641},[512,3604,3605],{"class":526}," alerts;\n",[512,3607,3609],{"class":514,"line":3608},64,[512,3610,985],{"class":526},[512,3612,3614],{"class":514,"line":3613},65,[512,3615,1113],{"emptyLinePlaceholder":1112},[512,3617,3619,3621,3624,3626,3628,3630],{"class":514,"line":3618},66,[512,3620,1045],{"class":641},[512,3622,3623],{"class":530}," current",[512,3625,1051],{"class":641},[512,3627,1054],{"class":641},[512,3629,2942],{"class":518},[512,3631,3632],{"class":526},"(watchlist);\n",[512,3634,3636,3638,3641,3643,3645,3648,3651,3654],{"class":514,"line":3635},67,[512,3637,1045],{"class":641},[512,3639,3640],{"class":530}," now",[512,3642,1051],{"class":641},[512,3644,1668],{"class":641},[512,3646,3647],{"class":518}," Date",[512,3649,3650],{"class":526},"().",[512,3652,3653],{"class":518},"toISOString",[512,3655,1107],{"class":526},[512,3657,3659,3661,3664,3666],{"class":514,"line":3658},68,[512,3660,1045],{"class":641},[512,3662,3663],{"class":530}," report",[512,3665,1051],{"class":641},[512,3667,2218],{"class":526},[512,3669,3671],{"class":514,"line":3670},69,[512,3672,1113],{"emptyLinePlaceholder":1112},[512,3674,3676,3678,3680,3682,3684,3686],{"class":514,"line":3675},70,[512,3677,1118],{"class":641},[512,3679,1121],{"class":526},[512,3681,1045],{"class":641},[512,3683,3100],{"class":530},[512,3685,1129],{"class":641},[512,3687,3688],{"class":526}," watchlist) {\n",[512,3690,3692,3694,3697,3699,3702,3705],{"class":514,"line":3691},71,[512,3693,1137],{"class":641},[512,3695,3696],{"class":530}," cur",[512,3698,1051],{"class":641},[512,3700,3701],{"class":526}," current.",[512,3703,3704],{"class":518},"get",[512,3706,3707],{"class":526},"(item.id);\n",[512,3709,3711,3713,3715,3717,3720,3723,3725,3728,3730,3732,3735,3738,3740,3743],{"class":514,"line":3710},72,[512,3712,3322],{"class":641},[512,3714,1121],{"class":526},[512,3716,1656],{"class":641},[512,3718,3719],{"class":526},"cur) { console.",[512,3721,3722],{"class":518},"warn",[512,3724,1060],{"class":526},[512,3726,3727],{"class":522},"`⚠️  ${",[512,3729,3292],{"class":526},[512,3731,105],{"class":522},[512,3733,3734],{"class":526},"label",[512,3736,3737],{"class":522},"}: not found — check id \u002F set_code \u002F q`",[512,3739,2020],{"class":526},[512,3741,3742],{"class":641},"continue",[512,3744,3745],{"class":526},"; }\n",[512,3747,3749],{"class":514,"line":3748},73,[512,3750,1113],{"emptyLinePlaceholder":1112},[512,3752,3754,3756,3759,3761,3764,3766,3768,3771,3773,3775,3778,3780,3783,3786,3788,3790,3792],{"class":514,"line":3753},74,[512,3755,1137],{"class":641},[512,3757,3758],{"class":530}," prev",[512,3760,1051],{"class":641},[512,3762,3763],{"class":526}," history.",[512,3765,2997],{"class":518},[512,3767,1711],{"class":526},[512,3769,3770],{"class":1697},"h",[512,3772,1662],{"class":526},[512,3774,1703],{"class":641},[512,3776,3777],{"class":526}," h.id ",[512,3779,1911],{"class":641},[512,3781,3782],{"class":526}," item.id).",[512,3784,3785],{"class":518},"at",[512,3787,1060],{"class":526},[512,3789,3260],{"class":641},[512,3791,966],{"class":530},[512,3793,1679],{"class":526},[512,3795,3797,3799,3801,3803,3805],{"class":514,"line":3796},75,[512,3798,1137],{"class":641},[512,3800,3312],{"class":530},[512,3802,1051],{"class":641},[512,3804,3287],{"class":518},[512,3806,3807],{"class":526},"(item, cur, prev);\n",[512,3809,3811],{"class":514,"line":3810},76,[512,3812,1113],{"emptyLinePlaceholder":1112},[512,3814,3816],{"class":514,"line":3815},77,[512,3817,3818],{"class":1039},"  \u002F\u002F Only store a snapshot when the source data has actually refreshed.\n",[512,3820,3822,3824,3826,3828,3831,3833,3836,3839,3842,3844,3847,3849],{"class":514,"line":3821},78,[512,3823,3322],{"class":641},[512,3825,1121],{"class":526},[512,3827,1656],{"class":641},[512,3829,3830],{"class":526},"prev ",[512,3832,2306],{"class":641},[512,3834,3835],{"class":526}," prev.tcgUpdated ",[512,3837,3838],{"class":641},"!==",[512,3840,3841],{"class":526}," cur.tcgUpdated ",[512,3843,2306],{"class":641},[512,3845,3846],{"class":526}," prev.cmUpdated ",[512,3848,3838],{"class":641},[512,3850,3851],{"class":526}," cur.cmUpdated) {\n",[512,3853,3855,3858,3860,3863,3865],{"class":514,"line":3854},79,[512,3856,3857],{"class":526},"    history.",[512,3859,2279],{"class":518},[512,3861,3862],{"class":526},"({ fetchedAt: now, ",[512,3864,2267],{"class":641},[512,3866,3867],{"class":526},"cur });\n",[512,3869,3871],{"class":514,"line":3870},80,[512,3872,2140],{"class":526},[512,3874,3876],{"class":514,"line":3875},81,[512,3877,1113],{"emptyLinePlaceholder":1112},[512,3879,3881,3884,3886],{"class":514,"line":3880},82,[512,3882,3883],{"class":526},"  report.",[512,3885,2279],{"class":518},[512,3887,3888],{"class":526},"({\n",[512,3890,3892],{"class":514,"line":3891},83,[512,3893,3894],{"class":526},"    card: item.label,\n",[512,3896,3898],{"class":514,"line":3897},84,[512,3899,3900],{"class":526},"    variant: cur.variant,\n",[512,3902,3904,3907,3910,3912,3915],{"class":514,"line":3903},85,[512,3905,3906],{"class":522},"    'TCG $'",[512,3908,3909],{"class":526},": cur.tcgMarket ",[512,3911,2320],{"class":641},[512,3913,3914],{"class":522}," '—'",[512,3916,722],{"class":526},[512,3918,3920,3923,3926,3928,3930],{"class":514,"line":3919},86,[512,3921,3922],{"class":522},"    'TCG low $'",[512,3924,3925],{"class":526},": cur.tcgLow ",[512,3927,2320],{"class":641},[512,3929,3914],{"class":522},[512,3931,722],{"class":526},[512,3933,3935,3938,3941,3943,3945],{"class":514,"line":3934},87,[512,3936,3937],{"class":522},"    'CM trend €'",[512,3939,3940],{"class":526},": cur.cmTrend ",[512,3942,2320],{"class":641},[512,3944,3914],{"class":522},[512,3946,722],{"class":526},[512,3948,3950,3953,3955],{"class":514,"line":3949},88,[512,3951,3952],{"class":526},"    alerts: alerts.",[512,3954,2297],{"class":530},[512,3956,722],{"class":526},[512,3958,3960],{"class":514,"line":3959},89,[512,3961,3962],{"class":526},"  });\n",[512,3964,3966,3968,3970,3972,3975,3977,3980,3982,3984,3987,3989,3991,3993,3995,3997,3999],{"class":514,"line":3965},90,[512,3967,1799],{"class":641},[512,3969,1121],{"class":526},[512,3971,1045],{"class":641},[512,3973,3974],{"class":530}," a",[512,3976,1129],{"class":641},[512,3978,3979],{"class":526}," alerts) console.",[512,3981,1153],{"class":518},[512,3983,1060],{"class":526},[512,3985,3986],{"class":522},"`🚨 ${",[512,3988,3292],{"class":526},[512,3990,105],{"class":522},[512,3992,3734],{"class":526},[512,3994,2064],{"class":522},[512,3996,72],{"class":526},[512,3998,1974],{"class":522},[512,4000,1679],{"class":526},[512,4002,4004],{"class":514,"line":4003},91,[512,4005,985],{"class":526},[512,4007,4009],{"class":514,"line":4008},92,[512,4010,1113],{"emptyLinePlaceholder":1112},[512,4012,4014,4017,4019],{"class":514,"line":4013},93,[512,4015,4016],{"class":526},"console.",[512,4018,339],{"class":518},[512,4020,4021],{"class":526},"(report);\n",[512,4023,4025,4027,4030,4032,4034,4036,4038,4040,4043,4046,4048,4050,4053],{"class":514,"line":4024},94,[512,4026,472],{"class":641},[512,4028,4029],{"class":518}," writeFile",[512,4031,1060],{"class":526},[512,4033,2711],{"class":522},[512,4035,1172],{"class":526},[512,4037,2725],{"class":530},[512,4039,105],{"class":526},[512,4041,4042],{"class":518},"stringify",[512,4044,4045],{"class":526},"(history, ",[512,4047,1015],{"class":530},[512,4049,1172],{"class":526},[512,4051,4052],{"class":530},"2",[512,4054,2693],{"class":526},[21,4056,4057],{},"Running it against the October 4 data gives:",[503,4059,4062],{"className":4060,"code":4061,"language":607},[605],"🚨 Zoro OP01-001 Parallel: CardMarket 1-day avg 25.4% vs 30-day avg\n┌─────────┬──────────────────────────┬─────────┬─────────┬───────────┬────────────┬────────┐\n│ (index) │ card                     │ variant │ TCG $   │ TCG low $ │ CM trend € │ alerts │\n├─────────┼──────────────────────────┼─────────┼─────────┼───────────┼────────────┼────────┤\n│ 0       │ 'Zoro OP01-001 Parallel' │ 'Foil'  │ 619.64  │ 695.01    │ 610.16     │ 1      │\n│ 1       │ 'Zoro OP01-001'          │ 'Normal'│ 2.06    │ 1.49      │ 1.87       │ 0      │\n│ 2       │ 'Luffy EB02-010 Dodgers' │ 'Foil'  │ 1534.62 │ 1599.95   │ '—'        │ 0      │\n└─────────┴──────────────────────────┴─────────┴─────────┴───────────┴────────────┴────────┘\n",[40,4063,4061],{"__ignoreMap":508},[21,4065,4066],{},"On the first run, the only alert that fires is the spike detector. The move alerts need a previous snapshot, so they can't fire until the second run. The spike alert is a real signal. CardMarket's 1-day average for the Zoro Parallel was €770, while the 7-day average was €630.83 and the 30-day average €613.93. A single day's average can come from just one or two sales, so read it as \"go look at recent sales\", not \"the price has changed\". That's also why we don't alert on the 7-day average, which was only 2.8% above the 30-day figure.",[21,4068,4069,4070,4072,4073,4075,4076,4078],{},"The Luffy row shows the null handling: there's no CardMarket price, so the table prints ",[40,4071,423],{}," and the tracker carries on. If you remove the optional chaining (",[40,4074,1209],{},") in ",[40,4077,3073],{},", this row crashes the script.",[107,4080,4082],{"id":4081},"scheduling-it","Scheduling it",[503,4084,4086],{"className":505,"code":4085,"language":507,"meta":508,"style":508},"crontab -e\n# every 6 hours; the updated_at check stops duplicate snapshots\n0 *\u002F6 * * * cd \u002Fhome\u002Fyou\u002Fop-price-tracker && BERRYWALLET_API_KEY=xxx \u002Fusr\u002Fbin\u002Fnode tracker.js >> tracker.log 2>&1\n",[40,4087,4088,4096,4101],{"__ignoreMap":508},[512,4089,4090,4093],{"class":514,"line":515},[512,4091,4092],{"class":518},"crontab",[512,4094,4095],{"class":530}," -e\n",[512,4097,4098],{"class":514,"line":537},[512,4099,4100],{"class":1039},"# every 6 hours; the updated_at check stops duplicate snapshots\n",[512,4102,4103,4105,4107,4110,4112,4114,4116,4119,4122,4125,4127,4130,4133,4136,4139,4142],{"class":514,"line":549},[512,4104,1169],{"class":518},[512,4106,1991],{"class":530},[512,4108,4109],{"class":522},"\u002F6",[512,4111,1991],{"class":530},[512,4113,1991],{"class":530},[512,4115,1991],{"class":530},[512,4117,4118],{"class":522}," cd",[512,4120,4121],{"class":522}," \u002Fhome\u002Fyou\u002Fop-price-tracker",[512,4123,4124],{"class":526}," && BERRYWALLET_API_KEY",[512,4126,648],{"class":641},[512,4128,4129],{"class":522},"xxx",[512,4131,4132],{"class":518}," \u002Fusr\u002Fbin\u002Fnode",[512,4134,4135],{"class":522}," tracker.js",[512,4137,4138],{"class":641}," >>",[512,4140,4141],{"class":522}," tracker.log",[512,4143,4144],{"class":641}," 2>&1\n",[21,4146,4147,4148,4150],{},"We haven't published a refresh frequency for One Piece prices in this tutorial, and you don't need one. The ",[40,4149,204],{}," check means running too often costs you a request but doesn't corrupt your history.",[282,4152,4154],{"id":4153},"handling-rate-limits","Handling Rate Limits",[21,4156,4157,4158,4162,4163,4165],{},"Rate limits depend on your plan and are documented in the ",[72,4159,4161],{"href":4160},"\u002Fberrywallet-docs#rate-limits","rate limits section of the BerryWallet docs",", with plan quotas on ",[72,4164,596],{"href":596},". We won't repeat the numbers here because they're per plan and can change. What matters is designing the tracker so the limit rarely matters:",[112,4167,4168,4182,4194,4203,4214],{},[115,4169,4170,4173,4174,4177,4178,4181],{},[26,4171,4172],{},"Batch by set."," This is the biggest saving. Watching 40 OP01 cards takes 2 requests at ",[40,4175,4176],{},"limit=100"," (159 rows), not 40. The ",[40,4179,4180],{},"fetchCurrent"," function groups the watchlist by set before making any calls.",[115,4183,4184,4187,4188,4190,4191,4193],{},[26,4185,4186],{},"Use search only for discovery."," A broad search like \"luffy\" has 250 matches, so looking up one card can take several pages. Use search once to find a card's ",[40,4189,123],{},", then set its ",[40,4192,2386],{}," in the watchlist once you know it. The search fallback is capped at 5 pages for this reason.",[115,4195,4196,128,4199,4202],{},[26,4197,4198],{},"Back off on 429 and 5xx.",[40,4200,4201],{},"apiGet"," retries with exponential backoff and jitter (1s, 2s, 4s, 8s) and then gives up loudly. A tracker that fails visibly is better than one that silently skips a run.",[115,4204,4205,4208,4209,4213],{},[26,4206,4207],{},"Never retry 4xx errors other than 429."," A 401 means your key is wrong and a 404 means your path is wrong. Retrying won't fix either one. The ",[72,4210,4212],{"href":4211},"\u002Fberrywallet-docs#errors","errors reference"," lists the codes.",[115,4215,4216,4219],{},[26,4217,4218],{},"Run requests in sequence."," The loops above run one request at a time on purpose. For a personal watchlist, parallel requests only make you hit the limit sooner.",[21,4221,4222,4223,4225],{},"If you outgrow per-set batching, for example tracking every card in every set, check the quotas on ",[72,4224,596],{"href":596},". Don't build a tighter retry loop to squeeze more out of a lower tier.",[282,4227,4229],{"id":4228},"going-to-production","Going to Production",[21,4231,4232],{},"The script above works well for a personal watchlist. Here's what to change when other people, or money, depend on it:",[290,4234,4235,4247,4264,4276,4288,4298,4312],{},[115,4236,4237,128,4240,4242,4243,4246],{},[26,4238,4239],{},"Move history out of a JSON file.",[40,4241,307],{}," is rewritten in full on every run, which is fine for a few thousand rows and wasteful beyond that. Use SQLite with a table keyed on ",[40,4244,4245],{},"(id, tcg_updated, cm_updated)"," and a unique constraint. The database then does the deduplication.",[115,4248,4249,4252,4253,1560,4256,4259,4260,105],{},[26,4250,4251],{},"Keep the two currencies in separate columns."," Store ",[40,4254,4255],{},"tcg_market_usd",[40,4257,4258],{},"cm_trend_eur"," as two columns and never merge them into one \"price\" field. If you want a single number, convert at query time with a dated exchange rate, and say which rate you used. This matters most if you're deciding between ",[72,4261,4263],{"href":4262},"\u002Fblog\u002Fone-piece-tcg-where-to-buy-sell-tcgplayer-cardmarket-ebay-2026","selling on TCGPlayer or CardMarket",[115,4265,4266,128,4269,4271,4272,4275],{},[26,4267,4268],{},"Treat the timestamps carefully.",[40,4270,204],{}," comes without a timezone offset (",[40,4273,4274],{},"2026-10-04T11:49:43.703577","), and the two sources update at different times. Store the strings exactly as received for deduplication, and don't treat them as local time.",[115,4277,4278,4281,4282,4285,4286,105],{},[26,4279,4280],{},"Send alerts somewhere you'll see them."," Replace ",[40,4283,4284],{},"console.log"," with a POST to a Discord or Slack webhook, or an email. Deduplicate alerts as well: a card sitting below its target fires on every run unless you record that you already notified for that ",[40,4287,204],{},[115,4289,4290,4293,4294,4297],{},[26,4291,4292],{},"Watch for missing cards."," The ",[40,4295,4296],{},"not found"," warning matters. A product can be merged, renamed or re-listed, and a watchlist entry that silently stops updating is worse than one that errors.",[115,4299,4300,4303,4304,4307,4308,4311],{},[26,4301,4302],{},"Store the key as a secret."," Use your platform's secret manager or a ",[40,4305,4306],{},".env"," file excluded by ",[40,4309,4310],{},".gitignore",". The key should never appear in the repo or in logs.",[115,4313,4314,4317,4318,4320],{},[26,4315,4316],{},"Fail loudly."," Exit with a non-zero code when ",[40,4319,4201],{}," throws, so cron monitoring (or a simple health check) notices a broken run.",[21,4322,4323,4324,4327,4328,4330],{},"A useful next step is a \"spread\" alert. When ",[40,4325,4326],{},"tcgLow"," is more than about 10% above ",[40,4329,3369],{},", as with the Zoro Parallel at $695.01 vs $619.64, sellers are asking well above recent sales. That usually means either a price rise that sales haven't caught up with yet, or optimistic listings that will drop. In either case, as a buyer, don't buy at the low listing. Wait and watch the market price for another week.",[21,4332,4333,4334,4338],{},"For a sense of which cards deserve a tracker, our ",[72,4335,4337],{"href":4336},"\u002Fblog\u002Fone-piece-tcg-rarity-guide-symbols-explained","One Piece TCG rarity guide"," explains why a Parallel leader and its base version can be 300 times apart in price.",[235,4340],{},[282,4342,4344],{"id":4343},"frequently-asked-questions","Frequently Asked Questions",[107,4346,4348],{"id":4347},"is-the-berrywallet-api-free","Is the BerryWallet API free?",[21,4350,4351,4352,4354],{},"There's a free tier that's enough for a personal watchlist like the one in this tutorial. Request quotas and paid tiers are listed on the ",[72,4353,597],{"href":596},". If you track whole sets, check your quota before scheduling frequent runs.",[107,4356,4358],{"id":4357},"whats-the-base-url-for-the-one-piece-card-api","What's the base URL for the One Piece card API?",[21,4360,4361,4364,4365,716,4367,1172,4370,1172,4373,1560,4376,4378,4379,4381,4382,4386],{},[40,4362,4363],{},"https:\u002F\u002Fapi.pokewallet.io",", with every One Piece route under ",[40,4366,583],{},[40,4368,4369],{},"\u002Fop\u002Fsets",[40,4371,4372],{},"\u002Fop\u002Fsets\u002F{set_code}",[40,4374,4375],{},"\u002Fop\u002Fsearch",[40,4377,1265],{},". Authenticate with the ",[40,4380,46],{}," header. The ",[72,4383,4385],{"href":4384},"\u002Fberrywallet-docs#quick-start","quick start"," has the full reference.",[107,4388,4390],{"id":4389},"why-does-the-same-card-number-show-up-twice","Why does the same card number show up twice?",[21,4392,4393,4394,4397,4398,4400,4401,4403],{},"One Piece reuses card numbers across variants and products. ",[40,4395,4396],{},"OP01-001"," covers both the $2.06 Zoro and the $619.64 Parallel, and ",[40,4399,131],{}," covers two different Luffy promos. Always store and match on the ",[40,4402,123],{}," field, which identifies a single product.",[107,4405,4407],{"id":4406},"are-cardmarket-prices-in-euros","Are CardMarket prices in euros?",[21,4409,4410,4411,1172,4414,1172,4417,1172,4419,1172,4422,4424],{},"Yes. CardMarket figures (",[40,4412,4413],{},"trend",[40,4415,4416],{},"avg",[40,4418,215],{},[40,4420,4421],{},"avg7",[40,4423,219],{},") are in EUR, and TCGPlayer figures are in USD. Keep them in separate fields and convert explicitly if you need to compare them.",[107,4426,4428],{"id":4427},"why-is-cardmarket-null-for-some-cards","Why is cardmarket null for some cards?",[21,4430,4431,4432,4434],{},"The product has no CardMarket listing that's matched to it. This is common for US-only promos like the Dodgers Luffy. Your code should treat ",[40,4433,1015],{}," as \"no data\", not zero, and keep tracking the TCGPlayer side.",[107,4436,4438],{"id":4437},"can-i-get-price-history-for-one-piece-cards-from-the-api","Can I get price history for One Piece cards from the API?",[21,4440,4441,4442,4446,4447,1172,4449,1560,4451,4453],{},"The One Piece endpoints in the ",[72,4443,4445],{"href":4444},"\u002Fberrywallet-docs","BerryWallet docs"," cover sets, cards, search, prices and images, with no separate history endpoint. That's why this tracker builds its own history from snapshots. CardMarket's ",[40,4448,215],{},[40,4450,4421],{},[40,4452,219],{}," fields give you some short-term trend information from day one.",[107,4455,4457],{"id":4456},"how-do-i-get-every-card-in-a-set","How do I get every card in a set?",[21,4459,4460,4461,4463,4464,4467,4468,4470,4471,4474],{},"Call ",[40,4462,4372],{}," for full card details or ",[40,4465,4466],{},"\u002Fop\u002Fprices?set_code=..."," for prices only. Both return a ",[40,4469,2355],{}," field, which is 159 for OP01. Use the ",[40,4472,4473],{},"apiGetAll"," helper from this tutorial to collect all the pages.",[107,4476,4478],{"id":4477},"can-i-write-this-in-python-instead-of-javascript","Can I write this in Python instead of JavaScript?",[21,4480,4481,4482,4485,4486,4489,4490,4492,4493,205],{},"Yes. The API is plain HTTPS with JSON, so ",[40,4483,4484],{},"requests"," or ",[40,4487,4488],{},"httpx"," work the same way. Keep the same structure: one client with backoff, per-set batching, matching on ",[40,4491,123],{},", and saving snapshots only when ",[40,4494,204],{},[235,4496],{},[16,4498,19,4500],{"style":4499},"background: rgba(107, 76, 154, 0.12); border-left: 4px solid #6B4C9A; padding: 1rem 1.5rem; margin: 1.5rem 0; border-radius: 4px;",[21,4501,24,4503,4506],{"style":4502},"margin: 0; font-size: 0.85rem; color: #e0e0e0;",[26,4504,4505],{},"💬 Price disclaimer:"," All prices in this article come from live BerryWallet API responses captured on October 4, 2026. TCGPlayer data (USD) was last updated at 11:49 and CardMarket data (EUR) at 10:22 that day. Prices have changed since then. The code examples are for educational purposes, and the alerts and signals described are not financial advice.\n  ",[235,4508],{},[282,4510,4512],{"id":4511},"track-one-piece-card-game-prices-live-with-berrywallet","Track One Piece Card Game Prices Live with BerryWallet",[21,4514,4515],{},"The tracker in this tutorial runs on the same TCGPlayer and CardMarket data that powers BerryWallet. If you'd rather not run a cron job yourself, the app gives you price alerts, price history and portfolio tracking with no code.",[112,4517,4518,4525,4532,4538,4545],{},[115,4519,4520,4521,4524],{},"🔔 ",[26,4522,4523],{},"Price alerts"," — get notified the moment a chase card hits your target",[115,4526,4527,4528,4531],{},"📊 ",[26,4529,4530],{},"Live prices"," from TCGPlayer & CardMarket as listings appear",[115,4533,174,4534,4537],{},[26,4535,4536],{},"Historical charts"," — track every card's price movement over time",[115,4539,4540,4541,4544],{},"💰 ",[26,4542,4543],{},"Portfolio tracking"," — see what your collection is actually worth",[115,4546,4547,4548,4551,4552],{},"💬 ",[26,4549,4550],{},"Community discussion"," in our ",[72,4553,4557],{"href":4554,"rel":4555},"https:\u002F\u002Fdiscord.gg\u002FznDxEjmDVP",[4556],"nofollow","Discord",[21,4559,4560],{},[26,4561,4562],{},"Get Started:",[112,4564,4565,4575,4582],{},[115,4566,4567,4568,4574],{},"🚀 ",[26,4569,4570],{},[72,4571,4573],{"href":4572},"\u002Fberrywallet","Start Tracking for Free"," — no credit card required",[115,4576,4577,4578],{},"📝 Read more ",[72,4579,4581],{"href":4580},"\u002Fblog","One Piece TCG guides and market analysis",[115,4583,4584,4585,4590],{},"📱 Follow ",[72,4586,4589],{"href":4587,"rel":4588},"https:\u002F\u002Finstagram.com\u002Fpokewallet.io",[4556],"@pokewallet.io"," for daily market coverage",[235,4592],{},[21,4594,4595],{},[442,4596,4597],{},"This article is for informational purposes only and is not financial advice. Details reflect information available at the time of writing; release dates, pricing and set contents are subject to change. Card prices are volatile — always verify current market pricing before buying or selling.",[4599,4600,4601],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":508,"searchDepth":537,"depth":537,"links":4603},[4604,4605,4606,4607,4608,4609,4615,4616,4617,4627],{"id":109,"depth":549,"text":110},{"id":284,"depth":537,"text":285},{"id":456,"depth":537,"text":457},{"id":576,"depth":537,"text":577},{"id":654,"depth":537,"text":655},{"id":1600,"depth":537,"text":1601,"children":4610},[4611,4612,4613,4614],{"id":1604,"depth":549,"text":1605},{"id":2376,"depth":549,"text":2377},{"id":2591,"depth":549,"text":2592},{"id":4081,"depth":549,"text":4082},{"id":4153,"depth":537,"text":4154},{"id":4228,"depth":537,"text":4229},{"id":4343,"depth":537,"text":4344,"children":4618},[4619,4620,4621,4622,4623,4624,4625,4626],{"id":4347,"depth":549,"text":4348},{"id":4357,"depth":549,"text":4358},{"id":4389,"depth":549,"text":4390},{"id":4406,"depth":549,"text":4407},{"id":4427,"depth":549,"text":4428},{"id":4437,"depth":549,"text":4438},{"id":4456,"depth":549,"text":4457},{"id":4477,"depth":549,"text":4478},{"id":4511,"depth":537,"text":4512},"Developer Guide","2026-10-07","Build a One Piece price tracker with the BerryWallet API: working Node.js code to fetch TCGPlayer and CardMarket prices, store history and fire price alerts.","md","\u002Fblog\u002Fone-piece\u002F2026\u002F10\u002Fbuild-one-piece-card-price-tracker-berrywallet-api-tutorial.webp","Roronoa Zoro, green-haired with three katana at his hip and a green bandana around his arm, and Monkey D. Luffy in his straw hat grinning as they lean over a glowing laptop of rising price charts, trading cards scattered across a ship's wooden desk, warm lantern light, a moonlit sea through the porthole.",{},"\u002Fblog\u002Fone-piece\u002F2026\u002F10\u002Fbuild-one-piece-card-price-tracker-berrywallet-api-tutorial","12 min read",{"title":5,"description":4630},"blog\u002Fone-piece\u002F2026\u002F10\u002Fbuild-one-piece-card-price-tracker-berrywallet-api-tutorial",[4640,4641,4642,4643],"one piece price tracker","berrywallet api tutorial","one piece card api code","track one piece card prices","SL_JltJMwunogIPBF3uXmpVwMV3Igm0ytHUygxFlWu4",1791354213635]