# itch.io Game, Review, Creator and Jam Scraper (`abotapi/itch-io-scraper`) Actor

Scrape itch.io games by genre, tag, platform, price band or keyword, plus creator catalogues and game jam results. Returns price and pay-what-you-want floor, rating, tags, platforms, files and the full review thread with creator replies. Incremental mode tracks changes.

- **URL**: https://apify.com/abotapi/itch-io-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 89.7% runs succeeded, 1 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

## itch.io Game, Review, Creator and Jam Scraper

itch.io Game, Review, Creator and Jam Scraper turns itch.io into structured game, review, creator and jam data. Browse the site's own game index with its own filters, search by keyword, follow a creator's whole catalogue, or read a game jam and its ranked results. Every game comes back with its price and its pay-what-you-want floor, its rating and rating count, its tags, platforms, file list and the jam it was submitted to; turn on the detail lookup and each game also brings its full review thread, including which replies came from the game's own creator. Export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **The review thread, with creator replies.** Other itch.io datasets stop at the star average. This one returns the individual comments, nested replies included, and flags every reply written by the game's own creator, so you can see not just the rating but how the studio actually answered.
- **The pay-what-you-want floor, not just the sticker price.** A large share of the catalogue is "name your own price". Both numbers come back separately, read from the page's own payload rather than from button text, so a free game, a donation game and a fixed-price game are never confused.
- **Five modes, one output contract.** Browse, keyword search, creator, jam and pasted links all produce the same flat record shape, so a mixed pull needs no branching downstream.
- **Jam results in full.** A ranked jam returns every entry's overall rank, its number of ratings, its adjusted score, and its rank and score under each individual criterion the jam scored on.
- **Creator profiles with public follower counts.** A creator record carries their catalogue, their outbound links, and the follower, post and topic counters, read exactly rather than from the abbreviated text the page shows.
- **Recurring monitoring built in.** Incremental mode returns only what changed since the last run of the same search, and Resume continues one interrupted pull without paying for rows you already have.

### Use Cases

- **Indie game researchers and journalists:** build genre or tag datasets with price, rating and platform coverage for market reports.
- **Community managers and developers:** pull a game's full review thread, creator replies included, to audit community sentiment over time.
- **Game jam organizers and analysts:** turn a jam's ranked result table into a leaderboard, or a breakdown by individual judging criterion.
- **Price and sales trackers:** watch a genre or tag for pay-what-you-want floors, discounts and price-band changes.
- **Talent scouts and publishers:** read a creator's whole catalogue plus their public follower count to gauge an indie studio's reach.
- **Curation and newsletter teams:** schedule incremental runs over a tag or genre to surface new releases automatically.

### Data You Get

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

| Field | Example |
|---|---|
| `recordType` | `"game"` (also `"developer"`, `"jam"`) |
| `recordId` | `"game-1"` |
| `gameId` | `1` |
| `title` | `"Sample Game Title"` |
| `url` | `"https://sample-studio.itch.io/sample-game"` |
| `developerName` / `developerUrl` | `"Sample Studio"` / `"https://sample-studio.itch.io"` |
| `genre` | `"Puzzle"` |
| `tags` | `["atmospheric", "pixel art", "singleplayer"]` |
| `platforms` | `["Windows", "macOS", "Linux"]` |
| `priceUsd` | `4.99` |
| `minimumPriceUsd` | `0` |
| `pricingModel` | `"name-your-own-price"` |
| `isOnSale` / `salePercent` | `true` / `40` |
| `rating` | `4.6` |
| `ratingCount` | `1234` |
| `status` | `"Released"` |
| `files` | `[{"name": "sample-game-win.zip", "sizeText": "120 MB", "platforms": ["Windows"]}]` |
| `jamName` | `"Sample Game Jam 2026"` |
| `reviewCount` / `reviewsReturned` | `210` / `20` |
| `reviews` | `[{"authorName": "player-one", "body": "Loved the ending.", "isDeveloperReply": false}]` |
| `changeType` | `"NEW"` (incremental mode only) |
| `scrapedAt` | `"2026-01-01T00:00:00Z"` |

`hasDemo`, `updateTitle`, `updatePublishedAt` and `updateUrl` are filled only when `fetchDetails` is on. `hasDemo` is true when the game page has a demo download section (or a file named as a demo) and false when the page was read and has none. The `update*` fields describe the newest post in the page's development log and are null when the game has none.

Creator records (`recordType: "developer"`) carry the creator's name, page links, outbound links, catalogue count and the public follower, post and topic counters. Jam records (`recordType: "jam"`) carry the jam's host, dates, joined and submission counts and, when the jam is ranked, the full `topEntries` result table with each entry's rank, number of ratings, adjusted score, and rank and score under every criterion the jam scored on.

### How to Use

1. Pick a **mode**: browse the game index with filters, search by keyword, pull named creators, read game jams, or paste site links.
2. Narrow it: for browse mode choose up to two filter slots (genre, tag, platform, price, jam entry, or an ordering other than the default); for the other modes fill in the keywords, creators, jams or links.
3. Turn on **Fetch details and reviews** if you want the full description, file list, exact price and the review thread; 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.

**Browse a genre, top rated first:**

```json
{
  "mode": "browse",
  "genres": ["Puzzle"],
  "sortBy": "top-rated",
  "maxItems": 100
}
```

**Browse with the full detail lookup and the review thread:**

```json
{
  "mode": "browse",
  "tags": ["horror"],
  "price": "free",
  "fetchDetails": true,
  "maxReviewsPerGame": 50,
  "maxItems": 25
}
```

**A creator's whole catalogue plus their follower count:**

```json
{
  "mode": "developer",
  "creators": ["sample-studio", "another-studio"],
  "fetchDetails": true
}
```

**A ranked jam with its full result table:**

```json
{
  "mode": "jam",
  "jams": ["sample-game-jam-2026"],
  "fetchDetails": true,
  "maxJamEntries": 100
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/itch-io-scraper").call(run_input={"mode": "browse", "genres": ["Puzzle"], "maxItems": 50})
for game in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(game["title"], game["priceUsd"], game["rating"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/itch-io-scraper').call({ mode: 'browse', genres: ['Puzzle'], 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.

#### What the site itself limits

Per-game and per-jam caps are finite: `maxReviewsPerGame` is limited to 500 comments per game and `maxJamEntries` to 1000 ranked entries per jam (`0` means 1000). A run that previously asked for more than that now receives at most those amounts; no other behaviour, state key or charge changed.

- **At most two filter slots per browse address.** Genres, tags, platform, price, jam entries and demo each use one slot, and any ordering other than the site default uses one as well. Asking for a third stops the run immediately with a message naming what to drop, rather than spending requests on an address the site will not serve.
- **Keyword search is one page, and a smaller filter surface.** The site serves a single page of results per keyword and publishes no second page; use browse mode for a large set. Search also carries only a platform option, a free-or-paid split and a demo flag, with no genre, tag, jam-entry or ordering option and no discount or price band. A filter the chosen mode cannot apply stops the run with a message naming it, so a run never comes back looking filtered when it is not.
- **A creator page and a jam page are single documents.** They publish no filter surface at all, so the listing filters above are not available in creator or jam mode. A browse scope also stops at the site's own depth limit of 200 pages (7,200 games); when that happens the run says so and, in incremental mode, will not mark anything expired from a scan it knows was incomplete.

#### Max results, per-scope budgets and a shared-scan gap to know about

**Max results** is one budget shared across every target in a run: several search keywords, several creators, several jams, or a mixed list of pasted links each get a fair share, so the first target does not spend the whole cap by itself. When a results list has to be cut short to stay inside that per-target share, the actor still reads every game on the page to decide what is new, and it remembers all of them as already scanned for the rest of the run, not only the handful that were actually returned.

**Who is affected:** only a run with **Max results** above `0` that reads **two or more game lists**: search mode with more than one keyword, or url mode with more than one pasted search or browse link. Browse mode (one address per run), a single keyword, creator mode and jam mode are not affected, because a creator or jam target returns one record and has no cut-off tail.

**What happens:** a game that fell in the cut-off tail of an earlier list, and that genuinely appears on a later list too, is skipped by the later list as well, so it is never returned by anyone. Worked example: two search keywords with **Max results** set to 6, so each keyword's share is 3. The first keyword's page lists 36 games; 3 are returned, but all 36 are now marked as scanned. The second keyword's page lists 2 games: one of the first keyword's 33 unreturned games, and one game nobody has seen. Only the new one is returned, and the run ends with **4 records instead of 6**, while the skipped game appears nowhere in the output. When the later page has plenty of other new games, the count still fills up, but that specific game is still missing.

**A restart does not clear it.** If the platform restarts the run part-way (for example, when a run is moved to another server), the actor's saved progress holds that same "already scanned" list, including the games that were never returned, so the restarted run skips them too. **Resume** (`resumeFromRunId`) is not affected: it reads ids from the earlier run's actual dataset, so only rows that were really returned are skipped.

**Workaround:** run one keyword or one link per call when you need every overlapping game, or set **Max results** to `0` (no cap), or high enough that no target's share is smaller than everything that target returns.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice. It reads the ids straight from that run's own dataset, so it is unaffected by the scanned-versus-returned gap above.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same search. Each record is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED` or `EXPIRED` (only after a run that scanned the whole tracked search, and only with `emitExpired`). A game's review thread and a jam entry's rank and score are deliberately excluded from change detection (a busy thread is entirely new rows within hours, and a rank moves with whichever ordering the run happened to read), but real movement still counts: `reviewCount`, `rating` and `ratingCount` are compared, so a game whose thread grew is still reported `UPDATED` with those fields named in `changedFields`. `stateKey` names or shares the stored state. `ignoreFieldsForChanges` lets you list extra field names (or a dotted path such as `files.size` for one key inside a list) that must not make a record `UPDATED` or appear in `changedFields`; the fields still appear in the output, and changing the list never turns the whole saved baseline into `UPDATED`. Scrape time, source URL, the demo flag and the latest devlog post (title, date, link), relative-time wording and numeric formatting (25.0 versus 25) never count as a change. With incremental mode off, output is exactly as before.

#### URL mode: supported links

URL mode with the **Site links** field left untouched now scrapes one example of each link shape below (the field was empty before). Clear the field or replace it to scrape only your own links.

| Link shape | Example | What you get |
|---|---|---|
| Browse, ordering and genre | `https://itch.io/games/top-rated/genre-puzzle` | game rows |
| Browse, paginated | `https://itch.io/games/genre-puzzle?page=2` | game rows from that page onward; a partial scan, so incremental mode never marks records `EXPIRED` from it |
| Browse, tag | `https://itch.io/games/tag-horror` | game rows |
| Browse, genre and tag | `https://itch.io/games/genre-puzzle/tag-pixel-art` | game rows |
| Browse, price, platform, jam entries | `https://itch.io/games/free`, `.../games/platform-web`, `.../games/in-jam` | game rows |
| Browse, ordering only or newest and genre | `https://itch.io/games/new-and-popular`, `https://itch.io/games/newest/genre-puzzle` | game rows |
| Keyword search | `https://itch.io/search?q=horror` | game rows |
| Game, its comments or devlog page | `https://gbpatch.itch.io/our-life`, `.../comments`, `.../devlog` | the game record |
| Creator page or profile | `https://gbpatch.itch.io/`, `https://itch.io/profile/gbpatch` | the creator record |
| Jam, results or entries | `https://itch.io/jam/gmtk-2024`, `.../results`, `.../entries` | the jam record |
| Jam listing | `https://itch.io/jams`, `https://itch.io/jams/past` | jam rows |

Any other address is skipped with a warning naming it. Search, creator and jam modes also accept plain names (a keyword, a creator handle, a jam name).

### 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, API & Integrations**.
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. If a connector fails, the run still succeeds and the dataset is unaffected. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `browse` | `browse`, `search`, `developer`, `jam` or `url`. |
| `queries` | array | `["horror"]` | Search mode keywords. One page of results per keyword. |
| `creators` | array | `["gbpatch"]` | Creator mode: handles, creator page links, or a link to one of their games. |
| `jams` | array | `["gmtk-2024"]` | Jam mode: jam handles or jam links. Leave empty to read a whole listing. |
| `jamScope` | select | `in-progress` | Which jam listing to read when no jams are named. |
| `urls` | array | see input form | URL mode: game, creator, jam, browse or search links, mixed freely. |
| `genres` | array | (none) | Browse mode only. Genres written the way the site writes them. One filter slot each. |
| `tags` | array | (none) | Browse mode only. Tags written the way they appear under a game. One filter slot each. |
| `jamEntries` | select | `any` | Browse mode only. Only jam entries, or only games outside a jam. One slot. |
| `sortBy` | select | `popular` | Browse mode only. The site's own orderings. Every value except the default uses one slot. |
| `platform` | select | `any` | Browse, search and url. Windows, macOS, Linux, Android, iOS, or playable in the browser. One browse slot. |
| `price` | select | `any` | Free is browse and search. Currently discounted, $5 or less and $15 or less are browse mode only. One browse slot. |
| `hasDemo` | boolean | `false` | Browse, search and url. Only games that publish a playable demo. One browse slot. |
| `minRating` | integer | `0` | Browse, search and url. Minimum rating, 0 to 5. Needs `fetchDetails`. |
| `minRatingCount` | integer | `0` | Browse, search and url. Minimum number of ratings. Needs `fetchDetails`. |
| `fetchDetails` | boolean | `false` | Read each record's own page. Adds one request per record and one Detail enrichment event. |
| `maxReviewsPerGame` | integer | `20` | Cap on comments returned per game. `0` returns the game without its thread. Values above 500 are limited to 500. |
| `includeJamEntries` | boolean | `true` | Also return each jam's ranked entry table. |
| `maxJamEntries` | integer | `20` | Cap on ranked entries per jam. `0` returns up to 1000 entries; larger values are limited to 1000. |
| `maxItems` | integer | `20` | The run's cap, shared across every target. `0` for unlimited. |
| `maxPages` | integer | `0` | Result pages per scope. `0` means no limit. |
| `resumeFromRunId` | string | (none) | Continue one interrupted run from its run or dataset id. |
| `incrementalMode` | boolean | `false` | Return only what changed since the last run of the same search. |
| `stateKey` | string | (none) | Name a monitoring campaign, or deliberately share state. |
| `emitUnchanged` | boolean | `false` | Also return unchanged rows. These are billed. |
| `emitExpired` | boolean | `false` | Also return rows that disappeared. These are billed. |
| `ignoreFieldsForChanges` | array | `[]` | Field names (or dotted paths into a list) left out of change detection. Incremental mode only. |
| `proxy` | object | Apify Proxy | Connection configuration. |
| `mcpConnectors` | array | (none) | Optional MCP connectors to pipe results into. |
| `notionParentPageUrl` | string | (none) | Notion parent page id, for the Notion connector. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run, 1 to 1000. |

### Output Example

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

```json
{
  "recordType": "game",
  "recordId": "game-1",
  "gameId": 1,
  "title": "Sample Game Title",
  "url": "https://sample-studio.itch.io/sample-game",
  "slug": "sample-game",
  "developerName": "Sample Studio",
  "developerUrl": "https://sample-studio.itch.io",
  "developerSlug": "sample-studio",
  "shortText": "A one line tagline for the game.",
  "coverImageUrl": "https://img.itch.zone/000000000/315x250/cover.png",
  "description": "The full page description appears here when fetchDetails is on.",
  "genre": "Puzzle",
  "tags": ["atmospheric", "pixel art"],
  "platforms": ["Windows", "macOS"],
  "madeWith": ["Godot"],
  "languages": ["English"],
  "inputs": ["Keyboard", "Mouse"],
  "accessibility": ["Subtitles"],
  "averageSession": "About an hour",
  "status": "Released",
  "publishedAt": "2026-01-01T00:00:00Z",
  "updatedAt": "2026-01-01T00:00:00Z",
  "priceUsd": 4.99,
  "minimumPriceUsd": 0,
  "pricingModel": "name-your-own-price",
  "isOnSale": false,
  "salePercent": null,
  "rating": 4.6,
  "ratingCount": 1234,
  "isBrowserPlayable": false,
  "hasDevlog": true,
  "hasDemo": false,
  "updateTitle": "Version 1.1 patch notes",
  "updateUrl": "https://sample-dev.itch.io/sample-game/devlog/1/version-1-1-patch-notes",
  "updatePublishedAt": "2026-04-16T22:23:00Z",
  "files": [
    { "name": "sample-game-win.zip", "sizeText": "120 MB", "platforms": ["Windows"] }
  ],
  "jamName": "Sample Game Jam 2026",
  "jamSlug": "sample-game-jam",
  "jamUrl": "https://itch.io/jam/sample-game-jam",
  "jamEntryId": 1,
  "reviewCount": 210,
  "reviewsReturned": 2,
  "reviews": [
    {
      "reviewId": 1,
      "url": "https://itch.io/post/1",
      "authorName": "player-one",
      "authorUrl": "https://itch.io/profile/player-one",
      "authorSlug": "player-one",
      "authorId": 1,
      "postedAt": "2026-01-01T00:00:00Z",
      "postedAgo": "3 days ago",
      "body": "Loved the ending.",
      "isReply": false,
      "replyDepth": 0,
      "parentReviewId": null,
      "isDeveloperReply": false
    },
    {
      "reviewId": 2,
      "url": "https://itch.io/post/2",
      "authorName": "Sample Studio",
      "authorUrl": "https://itch.io/profile/sample-studio",
      "authorSlug": "sample-studio",
      "authorId": 2,
      "postedAt": "2026-01-01T00:00:00Z",
      "postedAgo": "3 days ago",
      "body": "Thank you, a sequel is in the works.",
      "isReply": true,
      "replyDepth": 1,
      "parentReviewId": 1,
      "isDeveloperReply": true
    }
  ],
  "scrapedAt": "2026-01-01T00:00:00Z",
  "sourceUrl": "https://itch.io/games/genre-puzzle"
}
```

### Plan Requirement

The default Apify Proxy connection (datacenter pool) works for this site, and no residential pool is needed. Measured on this site: no verification page is served; the only refusal seen is a short-lived per-address rate limit (HTTP 429), which clears on retry a few seconds later. The actor paces itself, waits, rotates to a new connection and retries up to a hard cap. A residential pool is available if you specifically want one, but it is optional. If your account has no Apify Proxy access at all, the run stops with a message saying a paid plan is required.

### FAQ

#### How much does it cost?

You pay per record returned, plus one Detail enrichment event for each record where Fetch details and reviews actually ran and the record was returned rather than suppressed. Actor start is charged once per run. The **Pricing** tab shows the current rates. Use **Max results** to cap the cost of any run.

#### Is it legal to scrape itch.io?

This actor collects only publicly available game, review, creator and jam data. You are responsible for how you use it: follow itch.io's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Game descriptions, reviews and jam entries are the creators' and commenters' own words, not facts, and carry their own rights.

#### Can I get only new or changed games 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 for the same search, and unchanged ones are not billed unless Emit unchanged is on. EXPIRED rows for games that disappeared are produced only once a run has fully scanned the tracked search, never after a capped run, a resumed run, or a browse scope that hit the site's own 200-page depth limit.

#### What's the most common point of confusion?

The site itself serves at most two filter slots on a browse address, and a chosen ordering other than Popularity uses one of them. Asking for genre, tag, platform, price, jam entry and a non-default ordering all at once needs more slots than the site will serve, so the run stops before spending any requests and names exactly which filters to drop.

#### Why is a game missing, or the count short, when I searched several keywords or pasted several links?

**Max results** is split across targets, and when a list is cut short to fit a target's share, the actor still marks every game it read on that page as already scanned, not only the ones it returned. A later keyword or pasted search or browse link that lists one of those unreturned games skips it too, so it is never returned at all. Example: two keywords, **Max results** 6 (3 each). The first keyword's page has 36 games and returns 3. The second keyword's page has 2 games: one of the first keyword's 33 unreturned games, and one new game. The run returns **4 records instead of 6**, and the skipped game is missing from the output. A platform restart part-way through the run carries the same "already scanned" list forward, so it does not bring those games back; Resume from a previous run is not affected. Browse mode, a single keyword, creator mode and jam mode are not affected. Run one keyword or one link per call, or set **Max results** to `0` (or high enough that every target's share covers everything it returns).

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

If no results page can be loaded, the run stops with the message "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search.", so "no games matched" is never confused with "nothing could be read". Run it again in a few minutes. If only some pages could be read, you still get what was found, and the run's status message says how many requests were rejected.

#### 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 e-commerce data?

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

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/crazygames-scraper"><b>CrazyGames Scraper</b></a><br>Scrape CrazyGames games with ratings, upvotes and downvotes, total plays and likes...</td><td>🧩 <a href="https://apify.com/abotapi/roblox-scraper"><b>Roblox Scraper</b></a><br>Scrape Roblox experiences, marketplace items, users and communities. Player counts...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/mastodon-social-scraper"><b>Mastodon Scraper</b></a><br>Scrape trending Mastodon profiles and related posts from any Mastodon instance. Extract...</td><td>🧩 <a href="https://apify.com/abotapi/ok-ru-scraper"><b>OK.ru Scraper</b></a><br>Scrape public OK.ru users, groups, topics, posts, videos, games and greetings by keyword...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/pikabu-scraper"><b>Pikabu Scraper</b></a><br>Scrape Pikabu (Пикабу): keyword search, hot/new/best feeds, communities, tags, full story...</td><td>🛒 <a href="https://apify.com/abotapi/product-reviews-australia-scraper"><b>ProductReview.com.au Scraper</b></a><br>Scrape ProductReview.com.au products, businesses and customer reviews. Search by...</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/itch-io-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 'browse' to walk the site's game index with the filters below, 'search' to search by keyword, 'developer' to pull named creators with their catalogue, 'jam' to read game jams and their ranked results, or 'url' to scrape pasted game, creator, jam, browse and search links.

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

Only used when mode = search, ignored in the other modes. One or more keywords, for example 'horror', 'pixel art platformer' or a studio name. The site returns one page of matches per keyword.

## `creators` (type: `array`):

Only used when mode = developer, ignored in the other modes. Creator handles such as 'gbpatch', full creator page links, or a link to any game they made.

## `jams` (type: `array`):

Only used when mode = jam, ignored in the other modes. Jam handles such as 'gmtk-2024' or full jam links. Leave empty to read a whole listing instead.

## `jamScope` (type: `string`):

Only used when mode = jam and Jams is empty. Which of the site's own jam listings to read.

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

Only used when mode = url, ignored in the other modes. Mix freely: game pages such as https://studio.itch.io/game-name, creator pages, jam pages, browse addresses such as https://itch.io/games/genre-puzzle and search addresses.

## `genres` (type: `array`):

Browse mode only. Optional, one filter slot each. Genres written the way the site writes them, for example 'Puzzle', 'Visual Novel' or 'Role Playing'. Capitals, spaces and punctuation do not matter. Checked against the site's OWN published genre list every run; a genre the site does not publish is reported with close matches, and if none of your genres exist the run stops rather than quietly returning the unfiltered index. Note the site's own behaviour: its genre listing returns games in that genre AND closely related ones, so roughly half of a page carries that genre as its own primary genre.

## `tags` (type: `array`):

Browse mode only. Optional, one filter slot each. Tags written the way they appear under a game, for example 'horror', 'pixel art' or 'multiplayer'. A tag the site does not publish stops the run with a message naming it, rather than returning the unfiltered index.

## `jamEntries` (type: `string`):

Browse mode only. Optional, one filter slot. Keep only games that were submitted to a game jam, or only games that were not. The site's keyword search publishes no jam-entry option, so choosing one outside browse mode stops the run instead of being ignored.

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

Browse mode only. 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 is the only value that does NOT use one of the two filter slots, so leaving it alone frees a slot for a second filter. The site's keyword search returns results in its own relevance order and offers no ordering option, so choosing anything other than the default outside browse mode stops the run instead of being ignored.

## `platform` (type: `string`):

Browse, search and url modes. Optional. Keep only games released for this platform. Applied by the site itself either way, so it narrows at the source: as one browse filter slot, or as the search facet of the same name. A pasted browse address in url mode keeps its own platform segment instead.

## `price` (type: `string`):

Keep only games in this price band, applied by the site itself. The browse index publishes all five bands and each uses one filter slot. The keyword search publishes only the free or paid split, so 'Currently discounted', '$5 or less' and '$15 or less' are BROWSE ONLY: choosing one in search or url mode stops the run with a message rather than quietly returning every price.

## `hasDemo` (type: `boolean`):

Browse, search and url modes. Keep only games that publish a playable demo. One browse filter slot, or the search facet of the same name. A pasted browse address in url mode keeps its own segments instead.

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

Browse, search and url modes. Optional. Keep only games rated at or above this score on the site's own 0 to 5 scale. No listing publishes a rating, so this reads each game's own page and requires Fetch details and reviews. Use 0 for no minimum.

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

Browse, search and url modes. Optional. Keep only games rated at least this many times, which is the usual way to drop games whose average rests on a handful of votes. Reads each game's own page and requires Fetch details and reviews. Use 0 for no minimum.

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

Read each record's own page. For a game that adds the description, the full tag, language, input and accessibility lists, the publish and update stamps, the exact price and pay-what-you-want floor, the file list, the jam it was submitted to, the rating and rating count, and the REVIEW THREAD: every public comment with its author, the exact time, the body, the reply nesting, and whether the reply came from the game's own creator. For a creator it adds the public follower, post and topic counters. For a jam it adds the ranked result table. One extra request per record, billed as one Detail enrichment event per record returned.

## `maxReviewsPerGame` (type: `integer`):

Cap on how many comments from a game's review thread are returned. Use 0 to return the game without its thread. The game's own page already carries about 40 recent comments at no extra cost; a higher cap walks the thread further, which costs extra requests but is never charged more than the single Detail enrichment event for that game. Values above 500 are limited to 500; the field accepts at most 1000.

## `includeJamEntries` (type: `boolean`):

Used in jam mode and for a jam link pasted in url mode, together with Fetch details and reviews. On by default: the jam row is returned with its ranked entry table, each entry carrying its overall rank, its number of ratings, its adjusted score, and its rank and score under every criterion the jam scored on. Switch off to return the jam row alone.

## `maxJamEntries` (type: `integer`):

Cap on how many ranked entries are returned per jam. Use 0 for all entries, up to 1000; larger values are limited to 1000.

## `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 browse scope stops at page 200, which is 7,200 games; narrow the filters or change the ordering to reach past that.

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

Maximum number of result pages read per keyword, creator, jam or browse address. 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 200-page browse limit.

## `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. A record counts as UPDATED when any returned field changes, including price, sale, rating, rating count, tags, files, followers or jam counts. These never count and never appear in Changed fields: scrape time, source URL, the review rows (use the review count and rating instead), the demo flag and the latest devlog post (title, date, link), relative-time wording, and a jam's rank, score and ranked entries; add more with Ignore fields when detecting changes below. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately for each mode, target and filter 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 scope hit the site's own 200-page browse limit. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.

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

Optional. Field names that must not make a record UPDATED or appear in Changed fields, for example ratingCount or followers when you do not care about that movement. Use a dotted path such as files.size to ignore one key inside every item of a list. The fields still appear in the output; they are only left out of the comparison. Changing this list never marks the whole baseline as updated.

## `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": "browse",
  "queries": [
    "horror"
  ],
  "creators": [
    "gbpatch"
  ],
  "jams": [
    "gmtk-2024"
  ],
  "jamScope": "in-progress",
  "urls": [
    "https://itch.io/games/top-rated/genre-puzzle",
    "https://itch.io/games/genre-puzzle?page=2",
    "https://itch.io/games/tag-horror",
    "https://itch.io/games/genre-puzzle/tag-pixel-art",
    "https://itch.io/games/free",
    "https://itch.io/games/platform-web",
    "https://itch.io/games/in-jam",
    "https://itch.io/games/new-and-popular",
    "https://itch.io/games/newest/genre-puzzle",
    "https://itch.io/search?q=horror",
    "https://gbpatch.itch.io/our-life",
    "https://gbpatch.itch.io/our-life/comments",
    "https://gbpatch.itch.io/our-life/devlog",
    "https://gbpatch.itch.io/",
    "https://itch.io/profile/gbpatch",
    "https://itch.io/jam/gmtk-2024",
    "https://itch.io/jam/gmtk-2024/results",
    "https://itch.io/jam/gmtk-2024/entries",
    "https://itch.io/jams",
    "https://itch.io/jams/past"
  ],
  "jamEntries": "any",
  "sortBy": "popular",
  "platform": "any",
  "price": "any",
  "hasDemo": false,
  "minRating": 0,
  "minRatingCount": 0,
  "fetchDetails": false,
  "maxReviewsPerGame": 20,
  "includeJamEntries": true,
  "maxJamEntries": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `creators` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `changes` (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": "browse",
    "queries": [
        "horror"
    ],
    "creators": [
        "gbpatch"
    ],
    "jams": [
        "gmtk-2024"
    ],
    "urls": [
        "https://itch.io/games/top-rated/genre-puzzle",
        "https://itch.io/games/genre-puzzle?page=2",
        "https://itch.io/games/tag-horror",
        "https://itch.io/games/genre-puzzle/tag-pixel-art",
        "https://itch.io/games/free",
        "https://itch.io/games/platform-web",
        "https://itch.io/games/in-jam",
        "https://itch.io/games/new-and-popular",
        "https://itch.io/games/newest/genre-puzzle",
        "https://itch.io/search?q=horror",
        "https://gbpatch.itch.io/our-life",
        "https://gbpatch.itch.io/our-life/comments",
        "https://gbpatch.itch.io/our-life/devlog",
        "https://gbpatch.itch.io/",
        "https://itch.io/profile/gbpatch",
        "https://itch.io/jam/gmtk-2024",
        "https://itch.io/jam/gmtk-2024/results",
        "https://itch.io/jam/gmtk-2024/entries",
        "https://itch.io/jams",
        "https://itch.io/jams/past"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/itch-io-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": "browse",
    "queries": ["horror"],
    "creators": ["gbpatch"],
    "jams": ["gmtk-2024"],
    "urls": [
        "https://itch.io/games/top-rated/genre-puzzle",
        "https://itch.io/games/genre-puzzle?page=2",
        "https://itch.io/games/tag-horror",
        "https://itch.io/games/genre-puzzle/tag-pixel-art",
        "https://itch.io/games/free",
        "https://itch.io/games/platform-web",
        "https://itch.io/games/in-jam",
        "https://itch.io/games/new-and-popular",
        "https://itch.io/games/newest/genre-puzzle",
        "https://itch.io/search?q=horror",
        "https://gbpatch.itch.io/our-life",
        "https://gbpatch.itch.io/our-life/comments",
        "https://gbpatch.itch.io/our-life/devlog",
        "https://gbpatch.itch.io/",
        "https://itch.io/profile/gbpatch",
        "https://itch.io/jam/gmtk-2024",
        "https://itch.io/jam/gmtk-2024/results",
        "https://itch.io/jam/gmtk-2024/entries",
        "https://itch.io/jams",
        "https://itch.io/jams/past",
    ],
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/itch-io-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": "browse",
  "queries": [
    "horror"
  ],
  "creators": [
    "gbpatch"
  ],
  "jams": [
    "gmtk-2024"
  ],
  "urls": [
    "https://itch.io/games/top-rated/genre-puzzle",
    "https://itch.io/games/genre-puzzle?page=2",
    "https://itch.io/games/tag-horror",
    "https://itch.io/games/genre-puzzle/tag-pixel-art",
    "https://itch.io/games/free",
    "https://itch.io/games/platform-web",
    "https://itch.io/games/in-jam",
    "https://itch.io/games/new-and-popular",
    "https://itch.io/games/newest/genre-puzzle",
    "https://itch.io/search?q=horror",
    "https://gbpatch.itch.io/our-life",
    "https://gbpatch.itch.io/our-life/comments",
    "https://gbpatch.itch.io/our-life/devlog",
    "https://gbpatch.itch.io/",
    "https://itch.io/profile/gbpatch",
    "https://itch.io/jam/gmtk-2024",
    "https://itch.io/jam/gmtk-2024/results",
    "https://itch.io/jam/gmtk-2024/entries",
    "https://itch.io/jams",
    "https://itch.io/jams/past"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/itch-io-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/itch-io-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/p6p1d78J7gznsyVbJ/builds/hOiroizQPlnQqwION/openapi.json
