Data & API access
Every public bottle page has a machine-readable JSON twin, and AI agents get a dedicated /llms.txt — pull hammer prices, medians, and history programmatically, no account or API key required.
Every public price page on wsky1 has a JSON twin: append .json to the URL and you get the same data the page renders, as structured JSON. There's no API key, no auth, and no sign-up — the data is public and served straight from the edge. AI agents and LLMs also get a dedicated /llms.txt.
This page documents the two JSON endpoints, their exact response shapes, and how to read the values.
The two JSON endpoints
| Endpoint | Keyed by | Returns |
|---|---|---|
/prices/<slug>.json |
the human-readable bottle slug | a computed price summary + the 12 most recent observations |
/catalog/<key>.json |
the internal canonical key | the raw catalog row + up to 36 raw price points |
Both respond with Content-Type: application/json and Cache-Control: public, max-age=3600 (one hour), so hammer the edge freely — repeat requests are served from cache. Each page also advertises its twin as an alternate link (application/json) in the HTML <head>, so tools that follow alternates find it automatically.
/prices/<slug>.json — the bottle summary
The slug is the same one in the page URL — for example /prices/lagavulin-16-year-old:
curl https://wsky1.com/prices/lagavulin-16-year-old.json
{
"bottle": {
"displayName": "Lagavulin 16 Year Old",
"distillery": "Lagavulin",
"canonicalKey": "lagavulin|16|||||",
"ageYears": 16,
"bottlingYear": null,
"vintageYear": null,
"region": "Islay",
"abv": 430,
"sizeMl": 700
},
"priceSummary": {
"latestGBP": 92,
"medianGBP": 88,
"lowGBP": 71,
"highGBP": 140,
"currency": "GBP",
"dataPoints": 24
},
"recentHistory": [
{ "observedAt": "2026-06-15T00:00:00.000Z", "priceCents": 9200, "source": "swa", "lots": 3 }
]
}
bottle — identity fields for the matched canonical bottle:
displayName,distillery,region— human-readable strings (regionmay benull).ageYears,bottlingYear,vintageYear— integers ornullwhen the release has no such statement.abv— ABV × 10:430means 43.0% ABV. Divide by 10.sizeMl— bottle size in millilitres (defaults to700).canonicalKey— wsky1's internal identity string (lowercase, pipe-separated). Treat it as an opaque identifier — it's what you pass to/catalog/<key>.jsonbelow.
priceSummary — computed over the 24 most recent hammer observations:
latestGBP— the newest observed hammer price, in whole pounds.medianGBP,lowGBP,highGBP— median / min / max across those observations.currency— always"GBP".dataPoints— how many observations the summary is built from (0–24). All the*GBPfields arenullwhendataPointsis0.
recentHistory — the 12 most recent observations, newest first:
observedAt— ISO-8601 timestamp of the observation.priceCents— the hammer price in integer pennies (9200= £92.00).source— which feed the observation came from (see Where prices come from for the source codes).lots— how many auction lots that observation aggregates.
A bottle we know but have no price for still resolves — you get the bottle block with a dataPoints: 0 summary. An unknown slug returns { "error": "not found" } with HTTP 404.
/catalog/<key>.json — the raw catalog record
Keyed by the canonical key rather than a slug. The | separators must be URL-encoded as %7C:
curl 'https://wsky1.com/catalog/lagavulin%7C16%7C%7C%7C%7C%7C.json'
{
"bottle": {
"id": "cat_...",
"distillery": "Lagavulin",
"expression": "16 Year Old",
"displayName": "Lagavulin 16 Year Old",
"ageYears": 16,
"abv": 430,
"sizeMl": 700,
"bottler": null,
"caskType": null,
"edition": null,
"region": "Islay",
"canonicalKey": "lagavulin|16|||||",
"referencePriceCents": 8800
},
"priceHistory": [
{ "observedAt": "2026-06-15T00:00:00.000Z", "priceGBP": 92, "source": "swa" }
]
}
bottle— the full catalog row (all columns, includingexpression,bottler,caskNumber,caskType,edition,referencePriceCents, and internal bookkeeping fields). Same unit conventions as above —abvis ×10,referencePriceCentsis integer pennies.priceHistory— up to the 36 most recent raw observations, newest first. Here the price is pre-converted:priceGBPis whole pounds (no cents field), alongsideobservedAtandsource.
Use /catalog/<key>.json when you want the raw price series and the complete bottle record; use /prices/<slug>.json when you want the headline numbers already summarised.
Units & conventions
These hold across both endpoints:
- Currency is GBP, always. Every money value is British pounds sterling.
- Hammer prices, excluding buyer's premium. Nothing here includes the buyer's premium auction houses charge on top (typically 24–28%). To estimate what you'd actually pay, add it yourself. See Where prices come from.
- Cents vs. pounds. Fields ending
Cents(priceCents,referencePriceCents) are integer pennies; fields endingGBP(latestGBP,priceGBP, …) are whole pounds. abvis ABV × 10 — divide by 10 for the percentage.observedAtis an ISO-8601 timestamp; timestamps are date-level, so intraday time is00:00:00.sourcecodes map to auction houses (swa= Scotch Whisky Auctions, and so on) — the full list is in Where prices come from. The syntheticmarketaggregate is a per-distillery estimate, not a real house.
For AI agents & LLMs
wsky1 publishes two plain-text context files for LLM crawlers, and they're explicitly allowed:
/llms.txt— a concise site summary: what wsky1 is, an authority statement with citable example claims, pricing, data sources, valuation method, refresh cadence, and the key public pages./llms-full.txt— the extended version: full methodology, coverage numbers, an FAQ, and per-page descriptions.
Both are served as text/plain. If you're building an agent that cites whisky prices, read /llms-full.txt for the methodology and pull live numbers from the JSON twins above.
What's not public
/api/*endpoints are internal. They power the site's own UI (search, the demo, valuation) and are not a supported public API — don't build against them; they can change without notice. The public, stable data surface is the two.jsontwins on this page./app/*is the signed-in app and requires authentication — your own portfolio, cost basis, and gain/loss are never exposed through the public JSON.
Notes & limits
- No key, but be polite. There's no auth, but the one-hour edge cache means you rarely need to re-fetch — cache on your side and respect it.
- Indicative, not licensed. The data is aggregated from public auction results, not licensed from the houses. Treat every figure as indicative — it's for informational purposes only and is not investment advice.
- Freshness tracks the daily scrape cadence — see live counts and the last-scrape status on the live stats page.
Related
- Look up auction prices — the human-facing bottle pages these twins mirror.
- Live stats & data coverage — how much data sits behind these endpoints, refreshed live.
- Where prices come from — the source codes, houses, and the hammer-vs-premium math.