Data for agents and developers
Every deal MTB Deals shows is public as one JSON snapshot: 60 European, UK and US shops read every two hours, each deal with the shop's price next to the old one, the real cut against the pre-campaign price, the sizes in stock, a price history and a buy-or-wait verdict. No key, no sign-up, no affiliate links. This page says what the feed holds and where the pages are.
The JSON feed
- https://mtbdeals.co/api/deals: the full snapshot the app reads (about 2.3 MB raw, 300 KB compressed with gzip or brotli). GET only, no key, readable from any origin (
Access-Control-Allow-Origin: *), cached a minute in the browser and five minutes at the edge. A scan writes it at every even UTC hour (00:00, 02:00, … 22:00);checkedAtis the time of the scan it holds andnextCheckAtthe next one. - https://mtbdeals.co/api/deals?head=1: the head, the first page of the default list (48 deals, EU and UK shops, bikes, parts and gear, 10% off or more, best deals first) with the shop states and the market read, about 70 KB raw.
The OpenAPI description
https://mtbdeals.co/api/deals/openapi.json (OpenAPI 3.1) describes the feed field by field with its closed vocabularies (the shop ids, the categories, the campaign ids, the verdicts and fact codes) and every page URL below. Load it in any OpenAPI tool or hand it to an agent.
The snapshot
v: the format versioncheckedAt: when the scan ran (ISO 8601, UTC)nextCheckAt: when the next one runsshops: one entry per shop id: whether the read worked (ok,stale,blocked), the campaignsliveandannounced, the checkoutcodes, thefeedsreadproducts: the deals (below)market: the site-wide read: the season's phase, how many shops run a live campaign, how many bikes carry a real cut and its medianhealth: the last scan's and benchmark's self-checkalerts: whether email alerts are available
A product
id:<shop>:<key>, the key in share linksshop: the shop id, one of 60 (canyon, rose, radon, specialized, trek, giant, …; the full list with names and regions is in the OpenAPI description and undershopsin the feed)title, brand, url, img: the name, the brand, the shop's own product page (no affiliate parameters), the photoprice, was, cur, pct: the price now, the old price the shop shows, the currency (EUR, GBP or USD), percent offcat: mtb = MTB, emtb = e-MTB, parts = Parts, gear = Gear & apparel, gravel = Gravel & road, ebike = Other e-bikes, bike = Other bikesride, frame, wheels: xc, trail, enduro, dh, dirt; ht = hardtail, fs = full suspension; 29, mx, 27.5, 26, 28, 24, 20, 18, 16, 14, 12sizes, sizesFrom: the frame sizes in stock as the shop writes them (M, 51 cm, 19", EU 42 | L, S3: raw tokens; the app folds them onto one S–3XL scale for its filter) and where they were read (page,title, orfeedwithstock: falsefor the WooCommerce shops, whose size list is the options, not stock;none: truewhen the page listed sizes and none was in stock)camp: the campaign the shop files it under (singles-day, black-week, black-friday, stealth-sale, cyber-monday, cyber-week, boxing-day, winter-sale), null when nonefirst, low, prev, h: when we first saw it, its lowest price in our history, the previous price, recent price points as [day, price]real: the cut against the pre-campaign reference:ref,src(omnibus= the shop's own 30-day low,seen= the price when we first saw it),pct,verdict(below)used: Marktplaats asking prices for the model:n,min,medalt: other same-region shops with a comparable build:n, the cheapestshopandprice,dthe difference in percentsignal: the buy-or-wait verdictvand the fact codesr(below)hot: opened often here in the last two days
The verdicts
signal.v: buy "Buy now", good "Good price", wait "Wait", early "Too early to say". The fact codes in signal.r, the headline first: up, cheaper, ahead, early, nohist, real, scarce, cheapest, used, peak, headroom, low, altAhead, camp-price, noshow, peakdays, late, flat, popular; what each means is in the OpenAPI description and in llms-full.txt.
real.verdict: real (at least 5% under the reference price: a genuine new cut); same (within 5% of it: it cost that already (not a campaign cut, though it can still be a fair price)); up (above it: the price rose since the reference); new (seen for the first time, under two days of history); unknown (no reference price yet).
Pages and share links
https://mtbdeals.co/d/<id>: one deal as a page, with its Open Graph tags and a schema.org Product;https://mtbdeals.co/og/<id>.pngits 1200×630 share card.https://mtbdeals.co/list?<filters>andhttps://mtbdeals.co/og/list.png?<filters>: a filtered list as a link and its card; the keys are the app's (shop, regions, cats, brands, sizes, wheels, rides, frames, sig, max, min, q, camp, new, real, strict, sort).- The season's pages, rendered from the live snapshot: https://mtbdeals.co/black-friday-mountainbike-2026/,
https://mtbdeals.co/shop/<shop id>/,https://mtbdeals.co/brand/<slug>/,https://mtbdeals.co/category/<mtb|e-mtb|gravel|parts|gear>/, https://mtbdeals.co/calendar/. - The weekly market report:
https://mtbdeals.co/weekly/<iso week>/(e.g.https://mtbdeals.co/weekly/2026-W46/), one page per week from the same data as the Sunday mail, sealed on Sunday morning and kept; https://mtbdeals.co/weekly/ lists them. - https://mtbdeals.co/sitemap.xml (live), llms.txt and llms-full.txt for agents, the FAQ.
Licence
The data and the pages are published under Creative Commons Attribution 4.0 (CC BY 4.0): use them, copy them, build on them, with the attribution “MTB Deals (mtbdeals.co)” and a link back. The code is MIT-licensed (the LICENSE file in the repository).
Terms
Public, read-only, no key, no rate limit. Please cache the feed for five minutes (it changes every two hours at most) and link back to https://mtbdeals.co/ when you show its data. Prices are what the shops showed at the last check; the shop's own page is what counts. No affiliate links, no trackers, nothing is recorded about who reads the feed.