Place data sources
Status: Proposed 2026-09-24, first import run 2026-09-29; farmers markets and Open Library noted 2026-10-24. Ingest rules are on the ingest places page and the ingest media page.
Places come from three sources, in order of trust: curated, member-submitted, imported. All three land as pending and are approved by an admin before they appear, unless the source is listed in TRUSTED_SOURCES (ingest contract). Every imported row carries source and sourceId; on OSM rows sourceId is the osmId. Books, covered at the end of this page, go into the media library.
Curated
Sanctuaries and organizations are curated by hand from their own websites, because they are few, they matter most, and their details (visiting rules, volunteer programs) do not live in any open dataset. source: 'curated'.
Member-submitted
Any signed-in member can submit a place with a pin, a type, a vegan level, and a photo. source: 'user', submittedBy stored for moderation and never displayed.
OpenStreetMap
OpenStreetMap tags places with diet:vegan=yes (vegan options) and diet:vegan=only (fully vegan). A bounding box over Southern California (roughly Ventura to the Mexican border, the coast to the Inland Empire) contained 546 usable places on 2026-09-29, the day of the first import. This is the seed for restaurants, cafes, groceries, and shops.
The importer is scripts/seed-places-osm.ts in the API repo:
Rules:
- One tiled query covers both clauses:
nwr["diet:vegan"~"^(yes|only)$"]and namednwr["amenity"="marketplace"],out center tags. amenity=restauranttorestaurant,amenity=fast_foodonly whendiet:vegan=only(otherwise skipped),amenity=food_courttorestaurant,amenity=cafe|ice_creamtocafe,amenity=bar|pubtovenue,shop=supermarket|greengrocer|health_food|conveniencetogrocery, othershop=*toshop. Unknown tags are skipped and counted.diet:vegan=onlytoveganLevel: 'full',yesto'options'.- Upsert by
osmIdso re-runs update rather than duplicate. Existing approved places keep their approval; changed coordinates or names are flagged for review, not overwritten. - The importer sends a descriptive
User-Agentand runs against the Kumi Systems mirror, because the main Overpass instance refuses generic clients. --dry-runprints counts only.--approveexists for the first import of a curated source: new rows land approved and existing pending OSM rows are promoted, because a map with zero places helps nobody and the verification flow handles corrections from there. The importer never runs in CI and never against production without a human in the loop.- The first production import (2026-09-29) ran with
--approve: 546 OSM places plus 3 hand-checked sanctuaries fromscripts/data/sanctuaries.json.
The importer is moving to scripts/ingest/ and will POST to POST /api/ingest/places instead of writing to the database, with these additions from the ingest contract:
- The default map and list lean fully vegan:
fullsorts first and "include vegan options" is an opt-in toggle. amenity=fast_foodis imported only whendiet:vegan=only.- A
brandorbrand:wikidatatag setschain: true. Chains are hidden by default and never deleted. - Richer fields:
phone,postcode,opening_hours, andcuisine,wheelchair,outdoor_seating,takeaway,deliveryaskey:valuetags, plus a generated one-sentence description when OSM has none. areais derived from coordinates, never from the free-text city.
Farmers and produce markets
The same OpenStreetMap source, the same ODbL terms, and the same ingest:places:osm run. A named marketplace is imported when marketplace (or marketplace:type) is a farmers or produce value, or when the name reads as a farmers market, produce market, greenmarket, or farm stand. Flea, craft, and antique markets are left out.
They land as type: grocery with the tag market:farmers. Vegan level is options unless diet:vegan=only, which is full. chain stays false on these rows. Grocery is the existing buy-food type, so a current client can show a market, and the tag keeps a weekly market distinct from a store.
Attribution is unchanged. source is osm, sourceId is the OSM object id, and sourceUrl links to that object. Every Place with source: 'osm' shows "Data from OpenStreetMap contributors" on its page.
Community gardens
A second query, leisure=garden with garden:type=community plus named landuse=allotments, imports gardens as type: garden, veganLevel: full. Unnamed allotments are skipped.
Attribution: OpenStreetMap data is ODbL. Every Place with source: 'osm' shows "Data from OpenStreetMap contributors" on its page, and the map itself carries the OSM attribution through the tile style.
Open Library
Open Library feeds the media library. ingest:media:openlibrary searches the Open Library Search API for subject:veganism and subject:"animal rights" and upserts kind: book rows: title, authors, year, one ISBN, and a link to the work. Catalogue metadata is CC0. source is openlibrary. That source is outside TRUSTED_SOURCES, so books land draft until an admin publishes them.
Covers are downloaded, stripped of EXIF, XMP, and IPTC, and stored in the media bucket. The API receives the object key. Covers are re-hosted rather than hotlinked. The schedule, the one-second pause between calls, and --dry-run are on the automations page. The field list is on the data inventory.
What is not a source
- HappyCow, Yelp, Google Places. Their terms forbid bulk use, and Google Places would put a Google key back into the product (ADR-0006).
- Facebook and Instagram. They are never data sources. Social media scraping is not a source.
- The Trick Book's Chrome extension pattern (scraping Google Maps from a browser). Deprecated there, not carried here.
Refresh
Nightly at 12:42 AM Pacific: a run of the importer with --dry-run, then the real run. Farmers markets are in that same run. Six Overpass tiles failing in a row on both mirrors abort the importer, and the next night retries. The schedule and the stop conditions are on the automations page.