The VinylAI data API
Everything this site knows about record shops, and everything it has written about vinyl, is also published as JSON. No key, no account, no rate limit beyond ordinary fair use, CORS open to any origin, licensed CC BY 4.0. 1,431 shops in 77 cities across 19 countries, an index of 203 guides in eight languages, and 95 artist pages.
Start here
One request tells you the shape of everything else:
curl https://vinylai.app/api/v1/index.json Then take the dataset you want. Every file is static, served from the CDN, and regenerated on each site build:
curl https://vinylai.app/api/v1/record-stores.json
curl https://vinylai.app/api/v1/cities.json
curl https://vinylai.app/api/v1/guides.json
If you only need to know whether anything changed, fetch
status.json — it is under a kilobyte and carries
generated_at plus the row count of every dataset. Do that instead of refetching
the store directory, which is about 840 KB uncompressed.
The endpoints
| Endpoint | Size | What it returns |
|---|---|---|
/api/v1/record-stores.json | 1,431 shops | Every shop in the directory: name, address, WGS84 coordinates, website, specialities, and whether it buys records from the public. |
/api/v1/cities.json | 77 cities | The cities covered, each with a store count, a count of shops that buy from the public, and the page URL in every language it was built in. |
/api/v1/guides.json | 203 guides | Index of the written reference — topic, cluster, and which of the eight languages each guide exists in. |
/api/v1/artists.json | 95 artists | Artists with a collecting guide: primary genre, active years, country, and a pointer into Discogs’ catalogue. |
/api/v1/events.json | 10 events | Recurring vinyl events — Record Store Day and the record-fair calendar — with the organiser’s own site as the authority. |
/api/v1/status.json | — | When each dataset was last rebuilt, and how many rows it holds. Check this instead of refetching a megabyte. |
/api/v1/openapi.json | — | OpenAPI 3.1 over all of the above. |
The machine description of all of it is OpenAPI 3.1, and the RFC 9727 catalogue that points at it is /.well-known/api-catalog.
What the fields actually mean
This is the section a schema cannot carry, and it is the one worth reading. Each of these has produced a confidently wrong answer somewhere.
buys_records: false means unconfirmed, not “does not buy”
210 of the 1,431 shops are confirmed to buy records from the public. The other rows are not
shops that refuse — they are shops nobody has checked. Most independent record shops buy.
So a true is a positive fact you may state; a false is an absence of
information, and the correct phrasing is “not confirmed in the directory, call and ask”.
Telling someone a shop will not take their collection on the strength of a false
is the single most likely way to be wrong with this data.
Four fields are missing on purpose
The store rows carry no opening hours, no ratings or review counts, no telephone numbers and no third-party place identifiers. None of that is an oversight:
- Opening hours are the field that goes stale fastest, and being wrong about them sends somebody across a city to a closed shop. They are on the city page, next to the map, where they are read by a person who can see how old the page is.
- Ratings and review counts shown on the city pages are a third party’s aggregated content. Republishing them here under a CC BY grant would be licensing something that is not ours to license — and a rating frozen at build time misinforms besides.
- Telephone numbers. 1,277 business phone numbers in one machine-readable
file is a cold-call list, whatever it is labelled.
websitereaches the shop and cannot be auto-dialled in bulk. - Place identifiers from third-party mapping services mean nothing outside those services and are not republished.
The not_included block inside record-stores.json says the same thing
inside the payload, because a consumer reads the data and not the documentation.
page and pages
page is the canonical English URL. pages is an object keyed by
language code holding the same page in every language it was actually built in — which is not
always all eight. Link the language your reader is in; do not construct
/{lang}/… yourself, because a URL for a translation that does not exist is a
404 you invented.
languages on a guide is the real set
Every guide exists in English. A handful are missing one or two of the other seven, so the array is the built truth rather than a constant. Same rule as above: read it, do not assume it.
store_type is coarse
Almost every row reads both, meaning the shop sells new and secondhand. It is a
low-resolution field and the shop’s own website is the better authority on what it
actually stocks. specialties is more useful where it is populated.
lat / lon
WGS84 decimal degrees, the same convention every mapping library expects. They locate the shop,
not the centre of the city; the city’s own coordinates are in cities.json.
discogs_artist_id is a pointer, not a copy
The artist rows carry the artist’s identifier in Discogs’ catalogue so you can go and look up a discography there. No Discogs release, price or inventory data is reproduced in this API.
Freshness
These are static files rebuilt with the site. The failure mode worth watching is not a 5xx —
it is a stale generated_at in status.json. Shops
close and move; for anything that involves somebody travelling, the honest answer includes
“call first”.
Every page, as markdown
HTML is an expensive way to read a page. Each of this site’s pages has a markdown twin that is roughly a tenth of the bytes and carries the same words. Two ways to get it — agent stacks split about evenly between them:
curl -H "Accept: text/markdown" https://vinylai.app/record-stores/london/
curl https://vinylai.app/record-stores/london/index.md
Both work for any page on the site, in any of the eight languages. The twins carry a provenance
footer with the source URL and the licence, because a retrieved passage travels alone. They are
served with X-Robots-Tag: noindex — the same content at a second URL should not be
indexed twice, and that header does not stop an AI crawler reading the file.
For the whole site at once: /llms.txt is the index, and /llms-full.txt carries the primary pages in full.
MCP and A2A
An MCP server runs at https://vinylai.app/mcp (Streamable HTTP, JSON-RPC 2.0, no
authentication). It turns the same data into named tools, which is the difference between an
assistant that can answer “where can I sell my records in Berlin” and one that answers it
correctly.
curl -X POST https://vinylai.app/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Five read-only tools: find_record_stores, find_stores_that_buy_vinyl,
list_store_cities, find_vinyl_guide and get_artist_guide.
Every one returns the source URL next to the answer, so an agent that used a tool still has a
page to cite. The card is at /.well-known/mcp/server-card.json.
An A2A endpoint answering the same questions in natural language is at
https://vinylai.app/a2a, with its card at
/.well-known/agent-card.json. It is deliberately not a
language model: it routes on the entities it finds and says “I could not find that” rather than
improvising.
Agent skills
Four SKILL.md documents, indexed at /.well-known/agent-skills/index.json and digest-pinned, so a client can verify it loaded the bytes we published:
-
find-record-stores— Locating shops in a city, and knowing when the directory simply does not cover a place. -
sell-vinyl-records— Which shops buy from the public, and why a shop’s offer is not a marketplace price. -
identify-a-vinyl-pressing— Runout etchings, catalogue numbers, country of manufacture — and what a photo of a cover can never settle. -
value-a-vinyl-record— The four things that decide a record’s value and the three ways the usual answer is wrong.
Discovery
An agent that has fetched anything from this origin can find the rest from the response headers
alone — every response carries Link headers for the API catalogue, the OpenAPI
description, this page and /llms.txt. The documents themselves:
| Document | What it is |
|---|---|
/.well-known/api-catalog | RFC 9727 linkset: service-desc, service-doc, status and licence for the API. |
/.well-known/ai-catalog.json | ARD capability manifest — every dataset, skill and server card, with the queries each answers. |
/.well-known/agent-skills/index.json | Agent Skills Discovery 0.2.0 index; four SKILL.md documents, each digest-pinned. |
/.well-known/mcp/server-card.json | The MCP server card (SEP-1649). |
/.well-known/agent-card.json | The A2A agent card. |
/.well-known/oauth-protected-resource | RFC 9728 metadata declaring, explicitly, that there is no authorization server and nothing to authenticate. |
/auth.md | What an agent may do here, in prose. Short version: read everything, register nothing. |
/llms.txt | Index of the whole site as markdown. |
/llms-full.txt | Full text of the primary pages as one document. |
What there is no API for
Worth saying plainly, because the absence is a property of the product rather than a gap we forgot to fill:
- No writes. Nothing on this origin accepts data. A shop that wants its listing corrected or added writes to us through the contact page; there is no endpoint for it and no key that would open one.
- No user data, and no way to act for a user. Scanning a record, tracking a collection and buying a subscription all happen inside the app on the person’s own device, authenticated by their App Store or Google Play account. There is no server-side user API, so there is no credential an agent could hold — which is also why there is no OAuth authorization server here and why we do not publish metadata for one. The empty protected-resource document says exactly that, in the vocabulary a client already parses.
- No record identification endpoint. Photographing a record and identifying the pressing is what the VinylAI app does, on the device. It is not exposed as a public API.
- No Discogs data. We link to Discogs and we carry artist identifiers into it. We do not redistribute their catalogue, prices or inventory.
Licence and attribution
Everything under /api/v1/ is licensed
CC BY 4.0. Use it
commercially, build on it, redistribute it — with a link back to
vinylai.app or, better, to the specific page the row came
from. That attribution is the whole price.
The grant covers VinylAI’s own editorial text and the compilation. It is not a licence to any third party’s content, and none is included — which is precisely why the four fields above are absent.
Stability and fair use
/api/v1/ is versioned in the path. Fields will be added; existing fields will not
change meaning under the same name. If something has to change incompatibly it becomes
/api/v2/ and v1 stays where it is.
These are static files behind a CDN, so there is no quota to exceed and nothing to throttle.
Cache them anyway: they change when the site is rebuilt, and status.json exists so
you can find that out cheaply. Questions, corrections and anything that looks wrong:
the contact page.