Quick answer
Open Brewery DB is a free, keyless JSON API listing breweries, cideries and brewpubs — and it has two different ways of telling you "zero results," and they don't agree with each other unless you call a second, easy-to-miss endpoint. The Open Brewery DB Scraper resolves that ambiguity before it pages a single result, and hands back typed rows — location, contact, brewery type — for every matching record at $2.20 per 1,000 results.
Why can't a row count tell you if a search actually worked? 🧩
Page through Open Brewery DB's list endpoint with a filter that matches nothing, and you get back an empty page. Page through it with a filter that matches plenty, right up to the point where you've paged past the end, and you also get back an empty page. Watched from the outside, at the point your loop stops, both look identical: a request, a 200, an empty array.
Those are two completely different outcomes for a customer to receive. "Your byCity filter matched zero breweries" means check the spelling. "The source had exactly 340 breweries and you asked for 340" means the run did its job. Confusing them either reports a working search as broken, or reports a broken search as if it quietly finished — the same class of mistake that has cost this fleet real customer runs elsewhere, just approached from the opposite direction.
Open Brewery DB actually publishes the fix, in a /breweries/meta endpoint most integrators never discover, because it's a separate call from the one that returns rows. It answers the same filter with just a total. So before paging anything, this Actor resolves that total first: if meta says 0, the run finishes immediately with a status message naming the exact filter that came up empty, and never issues a single list request. If meta says the search is live, paging proceeds normally and a page shorter than the 200-row ceiling means "source exhausted," not "something broke" — which is exactly the distinction a raw row count can't make on its own.
(The one place meta doesn't apply is free-text name search and lat/lng distance search — Open Brewery DB doesn't expose totals for those two modes. The Actor knows which modes that affects and skips the meta call for them rather than guessing at a total the source never offers.)
When is "just page it" the right amount of engineering? 🛡️
Open Brewery DB caps every response at 200 rows and spreads six separate by_* filters — city, state, country, postal code, brewery type, plus a distinct /search endpoint for free-text name — across the API surface. That's real integration work to collapse into one input form, but the dataset behind it is not huge: this is a community-maintained, few-thousand-record directory, not a firehose. The interesting engineering call here wasn't building elaborate cursor management for a dataset that doesn't need it — it was building the one piece that actually matters for a small, filterable dataset: knowing the difference between empty and exhausted before you page a single row, and stopping cleanly the moment you have it.
Underneath the paging logic, the ordinary defences still run: retries with exponential backoff on 408/429/503 responses, honouring Retry-After, up to 5 attempts before surfacing an error; every record validated against a typed schema at write time, with a single malformed row skipped and logged rather than crashing the batch; and a maxResults cap enforced across pages so a request for 500 rows never silently becomes a request for all of them.
What you get per row
| Field | Example |
|---|---|
id |
5128df48-79fc-4f0f-8b52-d06be54d0cec |
name |
(405) Brewing Co |
brewery_type |
micro |
city / state_province / country
|
Norman / Oklahoma / United States
|
postal_code |
73069-8224 |
latitude / longitude
|
35.25738891 / -97.46818222
|
phone |
4058160490 |
website_url |
http://www.405brewing.com |
Pick a search mode — city, state, country, brewery type, name, postal code, or lat/lng radius — set a maxResults cap, and export as JSON, CSV or Excel from the run's dataset.
FAQ
Does this need an API key?
No — Open Brewery DB is a free, keyless public API, and this Actor is keyless too.
What happens if my filter matches nothing?
The run finishes successfully with zero rows and a status message naming the exact filter or query that matched nothing — resolved via the source's own /meta endpoint before any page request, not guessed at from an empty page.
Can I search by radius around a coordinate?
Yes — searchMode: "dist" with byDistLat/byDistLng. Results come back in the source's distance-sorted order.
How current is the data?
Rows reflect Open Brewery DB's live API response at run time — it's a community-maintained open-data project, updated on its own schedule, not ours.
Built by Devil Scrapes. We publish the traps we hit, because "it returned an empty array" is not the same answer twice.
Top comments (0)