# Untappd Beer, Brewery and Check-in Scraper (`abotapi/untappd-scraper`) Actor

Scrape Untappd beers, breweries, venues and check-ins by keyword, brewery, Top Rated chart or pasted link. Returns rating, rating count, style, ABV, IBU, brewery, check-in counters and recent check-ins with comments. Incremental mode tracks changes.

- **URL**: https://apify.com/abotapi/untappd-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 8 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Untappd Scraper: Beers, Breweries, Venues and Check-ins

Untappd Scraper turns Untappd into structured beer, brewery, venue and check-in data. Search the site's own beer, brewery or venue index by keyword and filters, pull one or more breweries together with their complete beer list, read the site's own Top Rated chart by style and country, or paste beer, brewery, venue, search and chart links. Beer records carry typed ids, style, ABV, IBU, rating, rating count and popularity at no extra cost (Top Rated chart rows are lighter, without popularity, styleId, breweryId or brewery coordinates); brewery and venue records carry their own location and identity fields instead. One opt-in per-record lookup adds the prose description, the exact check-in counters and the recent check-in feed: who drank it, their rating, comment, serving style, venue and badges. Export the dataset to JSON, CSV or Excel, or read it straight into your app through the API.

### Why This Scraper?

- **Four ways to find data.** Search by keyword and filters, look up named breweries with their full beer list, read the site's own Top Rated chart, or paste links. No need to build addresses by hand.
- **Complete brewery beer lists.** A brewery's beer list is walked past the site's own per-query result ceiling by splitting the beer id range, so the whole list is reachable however large it is, not just the first page a logged-out visitor sees.
- **Filters applied at the source.** Beer style, minimum rating, minimum rating count, ABV band, still-in-production and exclude-homebrew are all applied by the site's own index, so the result count is narrowed before download, not after.
- **Venues as first-class records.** Bars, taprooms and bottle shops come back with address, categories, verification and activity, alongside beers and breweries.
- **Typed fields, not raw strings.** Numbers come back as numbers (`abv: 5.4`, `rating: 3.5`, `totalCheckins: 1590000`), with the site's own abbreviated text (`"1.59M+"`) kept alongside rather than in place of the parsed value.
- **An opt-in detail lookup.** Switch on one toggle to add the prose description, the exact all-time, unique-drinker and last-four-weeks check-in counters, and the recent check-in feed, billed only for the records that actually receive the extra page request. If detail pages keep failing, the actor logs a warning and stops fetching them for the rest of the run; the remaining records come back with index fields only and are not charged for enrichment.
- **Built for recurring monitoring.** Incremental mode classifies every record as new, updated, unchanged, reappeared or expired against the previous run, and a genuinely blocked run fails loudly instead of returning an empty dataset.

### Use Cases

- **Beer catalog and inventory apps:** pull a brewery's complete beer list with style, ABV, IBU and rating to power a shop, taproom or cellar-tracking catalog.
- **Craft beer research and trend analysis:** read the Top Rated chart by style and country to compare how styles and regions rank against each other.
- **Venue discovery tools:** find taprooms, bars and bottle shops with address, categories and activity for a local guide or map.
- **Review and reputation monitoring:** watch a beer's or brewery's rating, rating count and recent check-in comments as they move over time.
- **Portfolio and competitor tracking:** schedule incremental runs against a list of breweries or styles to catch new releases and rating changes as they happen.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `entityType` | `"beer"` (also `"brewery"`, `"venue"`) |
| `recordId` | `"beer-100000"`, a stable cross-run key |
| `beerId` | `100000` |
| `name` | `"Example Wheat Ale"` |
| `url` | `"https://untappd.com/b/example-brewing-example-wheat-ale/100000"` |
| `style` / `styleId` | `"Wheat Beer - Witbier / Blanche"` / `47` |
| `abv` / `ibu` | `5.4` / `9` |
| `rating` / `ratingCount` | `3.5` / `625469` |
| `popularity` | `1590000` |
| `inProduction` / `isHomebrew` | `true` / `false` |
| `labelImage` | beer label image URL |
| `breweryId` / `breweryName` | `200000` / `"Example Brewing Company"` |
| `breweryUrl` | brewery page URL |
| `breweryLatitude` / `breweryLongitude` | `39.7392` / `-104.9903` |
| `description` | prose description (needs Fetch details and check-ins) |
| `totalCheckins` / `totalCheckinsText` | `1590000` / `"1.59M+"` (needs Fetch details and check-ins) |
| `uniqueUsers` | `709417` (needs Fetch details and check-ins) |
| `monthlyCheckins` | `3487` (needs Fetch details and check-ins) |
| `checkinsReturned` | `1` (needs Fetch details and check-ins) |
| `checkins` | the recent check-in feed (needs Fetch details and check-ins) |
| `chartRank` | rank on the Top Rated chart, `null` outside it |
| `changeType` | `"NEW"`, `"UPDATED"`, `"UNCHANGED"`, `"REAPPEARED"` or `"EXPIRED"` (incremental mode only) |

Brewery records replace the beer fields above with `breweryType`, `country`, `city`, `state`, `address`, `beerCount`, `outOfBusiness`, `latitude` and `longitude`, and add `website`, `instagramUrl`, `twitterUrl`, `facebookUrl`, `parentCompany`, `isOfficial` and the brewery's own `rating` and `ratingCount` when Fetch details is on. Venue records carry `address`, `city`, `state`, `country`, `categories`, `isVerified`, `isClosed`, `servesBeer`, `servesFood`, `servesWine`, `servesSpirits`, `popularity` and `popularityLast30Days`. Each entry in `checkins` carries its own `checkinId`, `checkinUrl`, `userName`, `userSlug`, `userProfileUrl`, `rating`, `comment`, `servingStyle`, `venueName`, `venueUrl`, `purchasedFrom`, `badges`, `toastCount`, `commentCount`, `photoUrl` and `createdAt`. Incremental mode adds `changedFields`, `firstSeenAt` and `lastSeenAt` alongside `changeType`. A beer or venue link pasted in url mode returns the exact same index-derived fields as a search result for that entity, never a narrower record.

### How to Use

1. Pick a **mode**: `search` (keyword and filters), `brewery` (named breweries plus their beer list), `topRated` (the site's own chart by style and/or country), or `url` (paste links).
2. Fill in the fields for that mode, and set the filters you want: style, minimum rating, minimum rating count, ABV band, production status.
3. Switch on **Fetch details and check-ins** if you want the description, the exact check-in counters and the recent check-in feed, and set **Max results** and **Max result pages** to control run size and cost.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**URL mode link shapes.** The Console example list shows every shape below. The shipped default list keeps one link per distinct target (nine: a beer, a brewery by address, a brewery by id, a venue, four Top Rated charts and a search); the beer id only, share query, photos page, beer list and name slug variants resolve to a target already in the default list, so an untouched url-mode run does not scrape them separately. Replace them with your own links. A venue link the site's search index does not list is read from the venue's own page instead. Any other address, and any venue link that returns no venue, is skipped with a warning naming it.

| Link shape | Example |
| --- | --- |
| Beer page | `https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839` |
| Beer id only | `https://untappd.com/beer/3839` |
| Beer page with share query | `https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839?ref=share` |
| Beer photos page | `https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839/photos` |
| Brewery address | `https://untappd.com/DogfishBeer` |
| Brewery beer list | `https://untappd.com/DogfishBeer/beer` |
| Brewery by id | `https://untappd.com/brewery/1` |
| Brewery with name slug | `https://untappd.com/w/dogfish-head-craft-brewery/1` |
| Venue | `https://untappd.com/v/bowditch-field/932830` |
| Top Rated, global | `https://untappd.com/beer/top_rated` |
| Top Rated by style | `https://untappd.com/beer/top_rated?type=belgian-quad` |
| Top Rated by country | `https://untappd.com/beer/top_rated?country=belgium` |
| Top Rated by style and country | `https://untappd.com/beer/top_rated?type=belgian-quad&country=belgium` |
| Search page (beer, brewery or venue) | `https://untappd.com/search?q=hazy+ipa&type=beer` |

**Search beers by keyword, narrowed by rating count:**

```json
{
  "mode": "search",
  "entityType": "beer",
  "queries": ["hazy ipa"],
  "minRatingCount": 500,
  "maxItems": 50
}
```

**A brewery with its complete beer list:**

```json
{
  "mode": "brewery",
  "breweries": ["Dogfish Head Craft Brewery"],
  "includeBreweryBeers": true,
  "maxItems": 0
}
```

**The Top Rated chart for one style and country:**

```json
{
  "mode": "topRated",
  "styles": ["Belgian Quadrupel"],
  "country": "Belgium"
}
```

**Paste links (beer, venue, brewery, search or chart):**

```json
{
  "mode": "url",
  "urls": [
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839",
    "https://untappd.com/beer/top_rated?type=belgian-quad"
  ]
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/untappd-scraper").call(run_input={"mode": "search", "queries": ["hazy ipa"], "maxItems": 50})
for beer in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(beer["name"], beer["rating"], beer["breweryName"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/untappd-scraper').call({ mode: 'search', queries: ['hazy ipa'], maxItems: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Max results, max pages and the site's own ceiling

One **keyword** search returns at most 1,000 results, which is the site's own per-query ceiling, not a cap this actor adds; the run logs a warning naming it and suggests narrowing with a style, a minimum rating count or an ABV band. A **filter-only beer search** (no keyword) and a **brewery's beer list** are not subject to that ceiling: the actor walks those by splitting the beer id range, so the whole set is reachable however large it is. A filter-only venue or brewery search has no such split and still stops at 1,000. **Max result pages** caps how many pages are read per scope; leave it at `0` to stop only at Max results, the end of the results, or the site's own ceiling.

#### Resume and recurring updates

- **`resumeFromRunId`** continues ONE interrupted run. Paste its run or dataset id and records already collected there are neither returned nor billed again.
- **`incrementalMode`** is for a schedule over the same search. The first run returns everything as `NEW`. Later runs return only `NEW`, `UPDATED` and `REAPPEARED` by default; turn on `emitUnchanged` or `emitExpired` to also return (and bill) unchanged or expired rows. A record can only be classified `REAPPEARED` if it was tombstoned `EXPIRED` by an earlier run, and a record can only be tombstoned `EXPIRED` when that earlier run had `emitExpired` on AND had proved a complete, uncapped, non-resumed scan of the tracked search; a capped run, a resumed run, a run that collected nothing, or a keyword scope that hit the site's own 1,000-result ceiling all skip expiry detection instead of tombstoning records they simply did not reach. `stateKey` names or shares the stored state, so two different searches never mix state with each other.

  **What is compared.** The beer, brewery and venue data fields (name, rating, `ratingCount`, `abv`, style, status, location and similar) decide `UPDATED` and appear in `changedFields`. These fields stay in the output but never appear in `changedFields` and never make a record `UPDATED`, because they move on every scrape: `scrapedAt`, `scrapedTimestamp`, `beerName`, `imageUrl`, `sourceUrl`, `chartRank`, `checkins` (including each check-in's `toastCount`, `commentCount` and `createdAt`), `checkinsReturned`, `popularity`, `totalCheckins`, `totalCheckinsText`, `monthlyCheckins`, `monthlyCheckinsText`, `uniqueUsers` and `uniqueUsersText`. Numbers compare the same whether stored as `5.0` or `5`. Add your own exclusions with **Ignore fields when detecting changes (incremental mode only)** (`ignoreFieldsForChanges`): field names, or a dotted path such as `checkins.rating`. Changing the ignore list never produces a one-time wave of `UPDATED` rows, because unchanged is decided by re-comparing the saved snapshot with the current ignore list.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search`, `brewery`, `topRated`, or `url`. |
| `entityType` | string | `beer` | Search mode only: `beer`, `brewery`, or `venue`. |
| `queries` | array | no default (prefill: `["hazy ipa"]`) | Search mode keywords. Leave empty to browse by the filters alone. |
| `breweries` | array | no default (prefill: `["Dogfish Head Craft Brewery"]`) | Brewery mode: names, numeric ids, vanity addresses, or full brewery links. |
| `includeBreweryBeers` | boolean | `true` | Also return the named brewery's complete beer list. |
| `urls` | array | nine distinct example links (a beer, a brewery by address, a brewery by id, a venue, four Top Rated charts and a search) | Url mode: beer, brewery, venue, search or chart links, or a bare numeric beer id. |
| `styles` | array | no default (empty) | Keep only these beer styles, checked against the site's own published list. |
| `country` | string | no default (empty) | Country filter, checked against the site's own published list. |
| `minRating` | integer | `0` | Minimum rating on the site's 0 to 5 scale. |
| `minRatingCount` | integer | `0` | Minimum number of ratings. |
| `minAbv` | integer | `0` | Minimum ABV percentage. |
| `maxAbv` | integer | no default (empty, no maximum) | Maximum ABV percentage. |
| `inProductionOnly` | boolean | `false` | Keep only beers still in production. |
| `excludeHomebrew` | boolean | `false` | Drop beers entered as homebrew. |
| `minCheckins` | integer | `0` | Minimum total check-ins. Requires `fetchDetails`; setting it without that toggle fails the run immediately, before any request is made. |
| `sortBy` | string | `popularity` | `popularity`, `popularity_recent`, `name_asc`, `name_desc`, `brewery_name`, `abv_desc`, or `abv_asc`. |
| `fetchDetails` | boolean | `false` | Read each record's own page: description, exact check-in counters and the recent check-in feed. Billed as one Detail enrichment event per record returned. |
| `maxCheckinsPerEntity` | integer | `20` | Cap on individual check-ins returned per record. `0` means the maximum of 100, not zero check-ins; leaving it blank uses the default of 20; use `1` for the smallest non-empty feed. The site's page shows about 20 recent check-ins to a logged-out visitor and its Show More button needs a login, so more than about 20 are not available. |
| `maxItems` | integer | `20` | Stop after this many records. `0` for unlimited. |
| `maxPages` | integer | `0` | Max result pages read per scope. `0` for no limit. |
| `resumeFromRunId` | string | no default (empty) | Continue one interrupted run from its run or dataset id. |
| `incrementalMode` | boolean | `false` | Return only new and changed records on repeated runs. |
| `stateKey` | string | no default (empty) | Name or share an incremental-mode state. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) unchanged records. |
| `emitExpired` | boolean | `false` | Also return (and bill) expired records, only after a complete scan. |
| `ignoreFieldsForChanges` | array | no default (empty) | Field names (or dotted paths) left out of change detection in incremental mode. They stay in the output. |
| `proxy` | object | `{"useApifyProxy": true}` | Connection settings. |
| `mcpConnectors` | array | no default (empty) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | no default (empty) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "entityType": "beer",
  "recordId": "beer-100000",
  "beerId": 100000,
  "name": "Example Wheat Ale",
  "url": "https://untappd.com/b/example-brewing-example-wheat-ale/100000",
  "style": "Wheat Beer - Witbier / Blanche",
  "styleId": 47,
  "abv": 5.4,
  "ibu": 9,
  "rating": 3.5,
  "ratingPrecise": 3.49888,
  "ratingCount": 625469,
  "popularity": 1590000,
  "inProduction": true,
  "isHomebrew": false,
  "hasCommunityAward": false,
  "labelImage": "https://assets.untappd.com/site/beer_logos/beer-100000_sm.jpeg",
  "breweryId": 200000,
  "breweryName": "Example Brewing Company",
  "breweryUrl": "https://untappd.com/w/example-brewing-company/200000",
  "breweryLatitude": 39.7392,
  "breweryLongitude": -104.9903,
  "description": "An unfiltered wheat ale spiced with coriander and orange peel.",
  "totalCheckins": 1590000,
  "totalCheckinsText": "1.59M+",
  "uniqueUsers": 709417,
  "monthlyCheckins": 3487,
  "checkinsReturned": 1,
  "checkins": [
    {
      "checkinId": 100000001,
      "checkinUrl": "https://untappd.com/user/example_user/checkin/100000001",
      "userName": "Example User",
      "userSlug": "example_user",
      "userProfileUrl": "https://untappd.com/user/example_user",
      "rating": 4,
      "comment": "Great with an orange slice.",
      "servingStyle": "Draft",
      "venueName": "Example Taproom",
      "venueUrl": "https://untappd.com/v/example-taproom/000000",
      "purchasedFrom": "Example Taproom",
      "badges": ["Earned the Example badge!"],
      "toastCount": 2,
      "commentCount": 0,
      "photoUrl": null,
      "createdAt": "Fri, 28 Aug 2026 03:23:16 +0000",
      "checkinText": "Example comment",
      "checkinDate": "Fri, 28 Aug 2026 03:23:16 +0000",
      "checkinPhotoUrl": null
    }
  ],
  "chartRank": null,
  "sourceUrl": null,
  "scrapedAt": "2026-08-28T12:00:00Z",
  "scrapedTimestamp": "2026-08-28T12:00:00Z"
}
```

#### Output tabs and run summary

The dataset has these tabs: **Overview** (all records), **Detail enrichment** (page fields added by Fetch details), **Check-ins** (one row per check-in, from the `checkins` list of each record, needs Fetch details), **Changes** (incremental mode rows), **Breweries** and **Venues**. Check-ins are not separate dataset items, so they add no extra result charge. After each run a summary is saved under the `OUTPUT` key of the key-value store (records by type and change type, enriched count, rejected requests).

#### Field aliases and what is not offered

Each check-in also carries `checkinText`, `checkinDate` and `checkinPhotoUrl` (same values as `comment`, `createdAt` and `photoUrl`). Beer records also carry `beerName` (= `name`) and `imageUrl` (= `labelImage`), and every record carries `scrapedTimestamp` (= `scrapedAt`), so exports built for other Untappd datasets keep working. Row-level errors are reported in the run status message rather than as a per-row `error` field.

A bare beer id (for example `3839`) is accepted in **Site links** (`urls`) in URL mode, and free-text searches go in `queries`. The "friends" and "you" check-in filters are not offered: they show only a logged-in account's own feed and this scraper never signs in.

Fields deliberately dropped from check-in rows: user avatar images and the per-link referral token, because neither carries data about the beer.

### Plan Requirement

The default connection setting works on a plan that includes Apify proxy access: the standard proxy pool is enough, and the residential group is never selected for you. The site challenges plain clients but serves real pages to the browser-grade connection this actor uses, and an individual connection that is refused is replaced with a fresh one automatically.

Apify free plans do not include proxy access. On such a plan the run continues on a backup connection and logs: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." When results come from it, the status reads "Served over the backup connection (the default connection was unavailable)." If no page can be read the run fails with one of three messages instead of returning an empty dataset: "The proxy connection was refused for this account ... a paid plan is required" (no proxy access), "No results: residential proxies are unavailable on this run ... and the backup connection could not load the requested Untappd pages" (residential selected but unavailable), or "Could not load any results page (the requests failed) ... This is a connection problem, not an empty search" (the site could not be reached). If you enter your own proxy URLs, check that their credentials are valid.

### FAQ

#### How much does it cost?

You pay per record returned (the Result event), a small Actor start charge of one event per GB of run memory (the default memory is 4 GB, so four start events per run), plus one Detail enrichment event for each *returned* record that received its own page request when **Fetch details and check-ins** is switched on. In incremental mode, a record whose page was read to check for changes but that turned out unchanged and suppressed is neither returned nor charged the enrichment event. The **Pricing** tab shows the current rates. Use **Max results** to cap the cost of any run.

#### Is it legal to scrape Untappd?

This actor collects only publicly available beer, brewery, venue and check-in data that a logged-out visitor can see. You are responsible for how you use it: follow Untappd's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Ratings, styles and check-in counts are generally facts, but check-in comments, photos and usernames are user-generated content and may carry their own rights.

#### Can I get only new or changed records on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated and reappeared records by default, and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### Why does a brewery's beer list return a different count than the brewery's own headline figure?

The returned list is the site's own search index for that brewery id, which can carry variants, collaborations and retired brews the brewery's own summary figure counts differently. Both numbers are reported exactly as the site gives them; neither is corrected into the other.

#### Why did my run fail instead of returning an empty dataset?

If every request is refused after retries, the run stops with a clear error naming it, so a real block is never confused with "no results found". A search that reads fine but genuinely matches nothing still succeeds, with an empty dataset and a status message saying so.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more wine and drinks data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🍷 <a href="https://apify.com/abotapi/danmurphys-scraper"><b>Dan Murphy’s Scraper</b></a><br>Scrape Dan Murphy’s products by category, keyword, or specials. Extract 45+ fields...</td><td>🍷 <a href="https://apify.com/abotapi/vivino-wine-data-scraper"><b>Vivino Wine Scraper</b></a><br>From $1.2/1K. Scrape Vivino.com for wine ratings, prices, taste profiles, food pairings...</td></tr>
<tr><td>🍷 <a href="https://apify.com/abotapi/wine-searcher-scraper"><b>Wine-Searcher Scraper</b></a><br>Look up wines on wine-searcher.com by name, URL, or LWIN code. Returns 30+ fields...</td><td>🥦 <a href="https://apify.com/abotapi/resq-club-surplus-food-scraper"><b>ResQ Club Scraper</b></a><br>Scrape ResQ Club surplus food offers in Finland, Sweden and Estonia: name, price, current...</td></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/instacart-grocery-price-scraper"><b>Instacart Scraper</b></a><br>Scrape Instacart grocery catalogs with per-store prices. Run keywords across several...</td><td>🥦 <a href="https://apify.com/abotapi/foodhero-surplus-grocery-scraper"><b>FoodHero Scraper</b></a><br>Scrape FoodHero surplus grocery deals across Canada by area, store or offer ID. Extract...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/untappd-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Choose 'search' to search by keyword and filters, 'brewery' to pull named breweries and their beer lists, 'topRated' to read the site's own Top Rated chart, or 'url' to scrape pasted beer, brewery, venue, search and chart links.

## `entityType` (type: `string`):

Only used when mode = search. Beers return name, brewery, style, ABV, IBU, rating and rating count. Breweries return type, location, beer count and popularity. Venues return address, categories and activity. Ignored in the other three modes.

## `queries` (type: `array`):

Only used when mode = search, ignored in the other modes. One or more keywords, for example 'hazy ipa', 'westvleteren' or 'brewpub denver'. Leave empty to browse by the filters alone.

## `breweries` (type: `array`):

Only used when mode = brewery, ignored in the other modes. Brewery names, numeric ids, vanity addresses such as /dogfishbeer, or full brewery links.

## `includeBreweryBeers` (type: `boolean`):

Used in brewery mode and for a brewery link pasted in url mode. On by default: the brewery row is returned together with its COMPLETE beer list, each beer carrying its own style, ABV, IBU, rating and rating count. The site's own brewery page shows only the first 25 beers to a logged out visitor, so this is the only way to read the rest. Switch off to return the brewery row alone.

## `urls` (type: `array`):

Only used when mode = url, ignored in the other modes. Mix freely: beer pages such as /b/<slug>/<id>, brewery pages, venue pages such as /v/<slug>/<id>, search pages and /beer/top_rated chart pages. A bare numeric beer id is also accepted.

## `styles` (type: `array`):

Optional. Keep only beers in these styles, written the way the site writes them, for example 'Belgian Quadrupel' or 'IPA - New England / Hazy'. Capitals, spaces and punctuation do not matter. Selecting several returns beers in any of them. Styles are checked against the site's OWN published style list every run; a style the site does not publish is reported with close matches, and if none of your styles exist the run stops rather than quietly returning unfiltered results.

## `country` (type: `string`):

Optional. Written the way the site writes it, for example 'Belgium' or 'United States'. On the Top Rated chart this is the site's own country filter. On a brewery or venue search it keeps only rows from that country. It has no effect on a beer search: the site publishes no country on a beer, only on its brewery. Checked against the site's own published country list; an unknown country stops the run with close matches.

## `minRating` (type: `integer`):

Optional. Keep only beers rated at or above this score on the site's own 0 to 5 scale. Applied by the site's index, so it narrows the result count at the source. Use 0 for no minimum.

## `minRatingCount` (type: `integer`):

Optional. Keep only beers that have been rated at least this many times, which is the usual way to filter out beers whose average is based on a handful of check-ins. Applied by the site's index. Use 0 for no minimum.

## `minAbv` (type: `integer`):

Optional. Keep only beers at or above this alcohol strength, as a percentage. Applied by the site's index. Use 0 for no minimum.

## `maxAbv` (type: `integer`):

Optional. Keep only beers at or below this alcohol strength, as a percentage. Applied by the site's index. Leave empty for no maximum.

## `inProductionOnly` (type: `boolean`):

Optional. Keep only beers the brewery still makes, dropping retired and one-off brews. Applied by the site's index.

## `excludeHomebrew` (type: `boolean`):

Optional. Drop beers entered as homebrew rather than as a commercial release. Applied by the site's index.

## `minCheckins` (type: `integer`):

Optional. Keep only records with at least this many all-time check-ins. That counter only exists on each beer, brewery or venue page, so this filter requires Fetch details and check-ins below and the run stops if it is set without it. Use 0 for no minimum.

## `sortBy` (type: `string`):

The order the site returns results in. Every value is one of the site's OWN orderings, applied at the source. Popularity is the site default and works for all three entity types. Beer name, brewery name and the two ABV orders apply to beers; recent popularity applies to venues. An ordering that does not exist for the entity you searched falls back to that entity's popularity order.

## `fetchDetails` (type: `boolean`):

Read each record's own page to add the prose description, the exact all-time, unique and monthly check-in counters, and the recent check-in feed: who drank it, their rating, their comment, the serving style, the venue, what badges they earned, any photo and when. One extra request per record, billed as one Detail enrichment event per record returned.

## `maxCheckinsPerEntity` (type: `integer`):

Cap on how many recent check-ins are returned per beer, brewery or venue. The description and the check-in counters are always complete regardless of this cap. Use 1 for the smallest non-empty feed; 0 means the maximum of 100, not zero check-ins; leaving the field blank uses the default of 20. The site's own page carries about 20 recent check-ins for a logged out visitor and its Show More button requires a login, so values above that return what the page has.

## `maxItems` (type: `integer`):

Maximum number of records to return across the whole run. This is the run's cap. Use 0 for unlimited. Note the site's own limit underneath it: one KEYWORD search returns at most 1,000 results however high you set this. A search with no keyword, and a brewery's beer list, have no such limit, because the actor walks those by splitting the id range.

## `maxPages` (type: `integer`):

Maximum number of result pages read per scope. 0 means no limit, which is the default: the run then stops only at Max results, at the end of the results, or at the site's own ceiling.

## `proxy` (type: `object`):

Apify Proxy is recommended. The standard pool is the default and is sufficient for this site.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull without returning or charging for records already collected there. Use this after an interrupted run. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns every matching record as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED records. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). Change detection compares the beer, brewery and venue data fields (name, rating, ratingCount, abv, style, status, location and similar); it ignores scrapedAt, sourceUrl, chartRank, popularity, the check-in feed, totalCheckins, monthlyCheckins and uniqueUsers (and their text forms), which move on every scrape. Add more with Ignore fields below. State is kept separately for each search, filter and lookup setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the mode, targets and filters, so different searches never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, so never when Max results capped it, when Resume was used, or when a keyword hit the site's own result ceiling. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional, incremental mode only. Field names that must never make a record UPDATED, for example rating or ratingCount. Use a dotted path such as checkins.rating for a field inside a list. The ignored fields stay in the output; they are only left out of the comparison and out of changedFields. Already always ignored: scrapedAt, sourceUrl, chartRank, popularity, the check-in feed and its counts, the abbreviated text counts, totalCheckins, monthlyCheckins, uniqueUsers, and per-check-in toastCount, commentCount and createdAt.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "entityType": "beer",
  "queries": [
    "hazy ipa"
  ],
  "breweries": [
    "Dogfish Head Craft Brewery"
  ],
  "includeBreweryBeers": true,
  "urls": [
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839",
    "https://untappd.com/beer/3839",
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839?ref=share",
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839/photos",
    "https://untappd.com/DogfishBeer",
    "https://untappd.com/DogfishBeer/beer",
    "https://untappd.com/brewery/1",
    "https://untappd.com/w/dogfish-head-craft-brewery/1",
    "https://untappd.com/v/bowditch-field/932830",
    "https://untappd.com/beer/top_rated",
    "https://untappd.com/beer/top_rated?type=belgian-quad",
    "https://untappd.com/beer/top_rated?country=belgium",
    "https://untappd.com/beer/top_rated?type=belgian-quad&country=belgium",
    "https://untappd.com/search?q=hazy+ipa&type=beer"
  ],
  "minRating": 0,
  "minRatingCount": 0,
  "minAbv": 0,
  "inProductionOnly": false,
  "excludeHomebrew": false,
  "minCheckins": 0,
  "sortBy": "popularity",
  "fetchDetails": false,
  "maxCheckinsPerEntity": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `checkins` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `breweries` (type: `string`):

No description

## `venues` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "search",
    "entityType": "beer",
    "queries": [
        "hazy ipa"
    ],
    "breweries": [
        "Dogfish Head Craft Brewery"
    ],
    "urls": [
        "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839",
        "https://untappd.com/beer/3839",
        "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839?ref=share",
        "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839/photos",
        "https://untappd.com/DogfishBeer",
        "https://untappd.com/DogfishBeer/beer",
        "https://untappd.com/brewery/1",
        "https://untappd.com/w/dogfish-head-craft-brewery/1",
        "https://untappd.com/v/bowditch-field/932830",
        "https://untappd.com/beer/top_rated",
        "https://untappd.com/beer/top_rated?type=belgian-quad",
        "https://untappd.com/beer/top_rated?country=belgium",
        "https://untappd.com/beer/top_rated?type=belgian-quad&country=belgium",
        "https://untappd.com/search?q=hazy+ipa&type=beer"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/untappd-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "search",
    "entityType": "beer",
    "queries": ["hazy ipa"],
    "breweries": ["Dogfish Head Craft Brewery"],
    "urls": [
        "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839",
        "https://untappd.com/beer/3839",
        "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839?ref=share",
        "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839/photos",
        "https://untappd.com/DogfishBeer",
        "https://untappd.com/DogfishBeer/beer",
        "https://untappd.com/brewery/1",
        "https://untappd.com/w/dogfish-head-craft-brewery/1",
        "https://untappd.com/v/bowditch-field/932830",
        "https://untappd.com/beer/top_rated",
        "https://untappd.com/beer/top_rated?type=belgian-quad",
        "https://untappd.com/beer/top_rated?country=belgium",
        "https://untappd.com/beer/top_rated?type=belgian-quad&country=belgium",
        "https://untappd.com/search?q=hazy+ipa&type=beer",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/untappd-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "entityType": "beer",
  "queries": [
    "hazy ipa"
  ],
  "breweries": [
    "Dogfish Head Craft Brewery"
  ],
  "urls": [
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839",
    "https://untappd.com/beer/3839",
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839?ref=share",
    "https://untappd.com/b/blue-moon-brewing-company-belgian-white/3839/photos",
    "https://untappd.com/DogfishBeer",
    "https://untappd.com/DogfishBeer/beer",
    "https://untappd.com/brewery/1",
    "https://untappd.com/w/dogfish-head-craft-brewery/1",
    "https://untappd.com/v/bowditch-field/932830",
    "https://untappd.com/beer/top_rated",
    "https://untappd.com/beer/top_rated?type=belgian-quad",
    "https://untappd.com/beer/top_rated?country=belgium",
    "https://untappd.com/beer/top_rated?type=belgian-quad&country=belgium",
    "https://untappd.com/search?q=hazy+ipa&type=beer"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false
}' |
apify call abotapi/untappd-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/untappd-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JttI8pRoJE61PXBUl/builds/s91ABsJWFnXkh6oiO/openapi.json
