# Kickstarter Scraper (`fetchfoundry-labs/kickstarter-scraper-cheapest`) Actor

Kickstarter projects from its own discovery API: funding, goal, % funded, backers, category, location, creator, dates -- plus reward tiers, followers and live counters. Clean one-row-per-project output; summaries, errors and cost in a separate run report.

- **URL**: https://apify.com/fetchfoundry-labs/kickstarter-scraper-cheapest.md
- **Developed by:** [Fetch Foundry](https://apify.com/fetchfoundry-labs) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.84 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Kickstarter Scraper — Projects, Funding, Backers, Rewards

Kickstarter projects straight from Kickstarter's own discovery API — goal,
pledged, % funded, backers, category, location, creator and dates — plus
optional reward tiers, followers and live counters.

**One clean row per project.** Summaries, errors, completeness and cost are
kept out of your data, in a separate run report — so the dataset is ready to
use, and you never have to guess whether it is complete.

> **v0.2 (October 2026).** Clean output, a run report with resume support,
> duplicate removal across queries and runs, ~50x less proxy traffic for
> details, and a more reliable Cloudflare strategy. See the
> [changelog](#changelog). Inputs from v0.1 keep working unchanged.

***

### Quick start

```json
{
  "queries": [
    {"state": "live", "categoryId": 16, "sort": "most_backed"},
    {"term": "board game", "state": "successful", "sort": "most_funded"}
  ],
  "maxProjectsPerQuery": 200,
  "includeProjectDetails": true,
  "includeLiveStats": true
}
```

You get **one row per project** in the dataset (example below), and a
**`RUN_REPORT`** record in the run's key-value store telling you, per query,
how many projects Kickstarter has, how many you got, whether the query is
complete, and the exact input to continue it.

**Inspect real output before you run anything** (public, no login):

- Sample dataset from a real run — [JSON](https://api.apify.com/v2/datasets/ex0dGGsnaUxnZhn4M/items?format=json\&clean=1) ·
  [CSV](https://api.apify.com/v2/datasets/ex0dGGsnaUxnZhn4M/items?format=csv\&clean=1) · [Excel](https://api.apify.com/v2/datasets/ex0dGGsnaUxnZhn4M/items?format=xlsx\&clean=1)
- Its run report — [RUN_REPORT (JSON)](https://api.apify.com/v2/datasets/CU3ZBvvkCdqGfU3Pc/items?format=json\&clean=1)
- Daily health-check log — [JSON](https://api.apify.com/v2/datasets/FQYVGpjIbKqRzoBSz/items?format=json\&clean=1)

***

### What you get

#### 1. The dataset: clean project rows

Real row from the sample run above (reward tiers shortened):

```json
{
  "projectId": 1471279538,
  "name": "VIOLOOP - PLUG-IN AI FOR YOUR COMPUTER",
  "state": "live",
  "projectUrl": "https://www.kickstarter.com/projects/bvio/violoop-plug-in-ai-for-your-computer",
  "categoryName": "Hardware",
  "parentCategoryName": "Technology",
  "goal": 78424,
  "pledged": 17409753,
  "currency": "HKD",
  "usdPledged": 2219802.17836761,
  "percentFunded": 22199.521830052025,
  "backersCount": 4191,
  "deadline": "2026-10-30T16:00:23+00:00",
  "launchedAt": "2026-09-15T16:00:23+00:00",
  "creatorName": "Bvio Tech",
  "countryName": "Hong Kong",
  "locationName": "Hong Kong, Hong Kong",
  "blurb": "Turn every PC agent-ready with plug-in AI that reads your screen, prepares tasks, and waits for your approval.",
  "_input": "query:state=live category=16 sort=most_backed",
  "_source": "kickstarter-discover-json",
  "_scrapedAt": "2026-10-10T09:39:37.230494+00:00",
  "recordType": "PROJECT",
  "slug": "violoop-plug-in-ai-for-your-computer",
  "rewardsUrl": "https://www.kickstarter.com/projects/bvio/violoop-plug-in-ai-for-your-computer/rewards",
  "convertedPledgedAmount": 2218226,
  "currencySymbol": "$",
  "currentCurrency": "USD",
  "fxRate": 0.12741288,
  "staticUsdRate": 0.12750337,
  "percentFundedFromUpstream": true,
  "country": "HK",
  "locationId": 2165352,
  "locationState": "Hong Kong Island",
  "locationCountry": "HK",
  "categoryId": 52,
  "parentCategoryId": 16,
  "categorySlug": "technology/hardware",
  "categoryMatchesRequested": true,
  "locationMatchesRequested": null,
  "creatorId": 2131188616,
  "creatorSlug": "bvio",
  "staffPick": true,
  "spotlight": false,
  "isLaunched": true,
  "prelaunchActivated": true,
  "disableCommunication": false,
  "createdAt": "2026-06-08T14:42:21+00:00",
  "stateChangedAt": "2026-09-15T16:00:27+00:00",
  "resultPage": 1,
  "resultRank": 1,
  "detailFetched": true,
  "statsFetched": true,
  "liveBackersCount": 4191,
  "livePledged": 17409753,
  "liveCommentsCount": 603,
  "liveState": "live",
  "livePercentFunded": 22199,
  "watchesCount": 8659,
  "rewardTierCount": 3,
  "addOnCount": 4,
  "updatesCount": 4,
  "updatesCountExact": true,
  "commentsCount": 603,
  "topLevelCommentsCount": 330,
  "faqsCount": 50,
  "projectWeLove": true,
  "creatorBackingsCount": 0,
  "creatorLaunchedProjects": 1,
  "detailSource": "graphql",
  "rewardTiersScope": "public",
  "dataComplete": true,
  "photoUrl": "https://i.kickstarter.com/assets/055/177/242/...jpg",
  "rewards": [
    {
      "rewardId": 11254735,
      "title": "Super Early Bird - Violoop",
      "minimum": 3128,
      "currency": "HKD",
      "convertedMinimum": 399,
      "convertedCurrency": "USD",
      "description": "Violoop is the first plug-in AI operator for any computer. It sees your screen, understands your workflow, and gets tasks done across apps—powered by on-device AI, local memory, and a physical confirmation key that keeps you in control.\n\n🚚 Shipping & Taxes Included\n🛡️ Guaranteed Delivery by Violoop\n🌍 Shipping to Most Countries & Regions",
      "backersCount": 3683,
      "available": true,
      "limit": null,
      "remaining": null,
      "limitPerBacker": 1,
      "estimatedDeliveryOn": "2026-11-01",
      "shippingPreference": "unrestricted",
      "shippingSummary": "Anywhere in the world",
      "rewardType": "base"
    },
    "... 2 more tiers"
  ]
}
```

The Console's Output tab has two tables:

- **Overview (25 key columns)** — funding, backers, category, dates, creator, location.
- **All fields** — every column (Apify's standard view).

Every row has the same columns whichever options you choose (options only fill more of them).

#### 2. The run report: everything that is not a project

Stored as `RUN_REPORT` in the run's default key-value store (Console: run →
*Storage* → *Key-value store*; API: the `runReport` link in the run's output).
Excerpt from the sample run:

```json
{
  "version": "0.2",
  "isComplete": false,
  "summary": {
    "projectsInDataset": 36,
    "queries": 3,
    "queriesFailed": 0,
    "queriesIncomplete": 3,
    "duplicatesRemovedAcrossQueries": 0,
    "excludedByHistoryOrList": 0
  },
  "queries": [
    {
      "label": "query:state=live category=16 sort=most_backed",
      "status": "ok",
      "totalHits": 373,
      "estimatedReachable": 373,
      "estimatedUnreachable": 0,
      "projectsFound": 12,
      "projectsPushed": 12,
      "pagesFetched": 1,
      "stoppedBecause": "max_projects_reached",
      "complete": false,
      "resumeFrom": {
        "term": null,
        "state": "live",
        "categoryId": 16,
        "woeId": null,
        "sort": "most_backed",
        "staffPicks": false,
        "raised": null,
        "goal": null,
        "startPage": 2
      },
      "categoryFilterHonoured": true,
      "categoryMatchRate": 1.0
    },
    "... one entry per query"
  ],
  "errors": [],
  "resumeInput": {
    "queries": [
      "... the resumeFrom of every query that did not finish"
    ]
  },
  "cost": {
    "requests": {
      "discover": 3,
      "graph": 6,
      "page": 0,
      "stats": 0,
      "warmup": 1
    },
    "bytesTransferred": {
      "discover": 57823,
      "graph": 48340,
      "page": 0,
      "stats": 0,
      "warmup": 175959
    },
    "approxProxyGigabytes": 0.00028,
    "retries": 0,
    "cloudflareChallenges": 0,
    "proxy": {
      "useApifyProxy": true,
      "apifyProxyGroups": [
        "RESIDENTIAL"
      ]
    },
    "tlsProfiles": {
      "safari17_0": {
        "passed": 10,
        "challenged": 0
      }
    }
  },
  "statusMessage": "36 project(s) from 3 query(ies). 3 query(ies) stopped at your maxProjectsPerQuery -- RUN_REPORT.resumeInput continues them without duplicates"
}
```

- `isComplete` — `true` only if every query was walked to its real end with
  no failure. A query that stopped at your `maxProjectsPerQuery`, at
  Kickstarter's 2,400-row ceiling or on an error makes it `false`.
- `queries[]` — one entry per query: Kickstarter's own `totalHits`, how much
  of it is reachable, what was returned, why it stopped, whether category /
  location filters were really applied, and `resumeFrom`.
- `errors[]` — failed queries with a reason. **A failed query adds nothing to
  your dataset**; it appears here (and in the run's status message) instead.
- `resumeInput` — paste it as the next run's input to continue every
  unfinished query exactly where this run stopped.
- `cost` — requests by kind, bytes transferred, retries, Cloudflare
  challenges and which TLS profiles worked.

The run list in the Console stays tidy: every run shows the platform's usual
*"Actor succeeded with 360 results in the dataset"* (or *"Actor finished with
… -- 1 query(ies) failed, see RUN_REPORT"*). The full sentence, e.g. *"360
project(s) from 3 query(ies). 3 query(ies) stopped at your maxProjectsPerQuery
\-- RUN_REPORT.resumeInput continues them without duplicates"*, is
`RUN_REPORT.statusMessage`.

Prefer everything in one table, as in v0.1? Set
`includeDiagnosticsInDataset: true` and the per-query `SEARCH_SUMMARY` and
`ERROR` rows are appended to the dataset (filter on `recordType`).

***

### Field reference

#### Project

| Field | Meaning |
|---|---|
| `projectId` | Kickstarter's numeric project id (stable; use it to deduplicate). |
| `name`, `blurb`, `slug` | Title, short description, URL slug. |
| `projectUrl`, `rewardsUrl` | Project page and its rewards page. |
| `state` | `live`, `successful`, `failed`, `canceled`, `upcoming` (and the occasional other upstream state). |
| `goal` | Funding goal, in the project's own `currency`. |
| `pledged` | Amount pledged, in the project's own `currency`. |
| `currency`, `currencySymbol` | ISO currency of `goal`/`pledged`, and its symbol. |
| `usdPledged` | `pledged` converted to USD with Kickstarter's static rate at the time. |
| `convertedPledgedAmount`, `currentCurrency`, `fxRate`, `staticUsdRate` | Kickstarter's own conversion to the viewer currency and the rates used. |
| `percentFunded` | `pledged / goal × 100`. Taken from Kickstarter when present, otherwise computed. |
| `percentFundedFromUpstream` | `true` if `percentFunded` came from Kickstarter, `false` if computed. |
| `backersCount` | Number of backers. |
| `staffPick`, `spotlight` | Kickstarter "Projects We Love" flag (listing version) and spotlight page flag. |
| `isLaunched`, `prelaunchActivated` | Launched yet; pre-launch page was enabled. |
| `disableCommunication` | Creator has turned off messages. |
| `createdAt`, `launchedAt`, `deadline`, `stateChangedAt` | ISO 8601 UTC timestamps. |
| `photoUrl` | Main project image. |

#### Category, location, creator

| Field | Meaning |
|---|---|
| `categoryId`, `categoryName`, `categorySlug` | The project's (sub)category. |
| `parentCategoryId`, `parentCategoryName` | Its parent category (e.g. Technology). |
| `categoryMatchesRequested` | When you filtered by category: did this row really match it? (`null` if no category filter.) See *Query limits*. |
| `country`, `countryName` | Project country (ISO code and name). |
| `locationId`, `locationName`, `locationState`, `locationCountry` | Kickstarter location ("WOE id"), display name, region, country. |
| `locationMatchesRequested` | When you filtered by `woeId`: did this row match it? |
| `creatorId`, `creatorName`, `creatorSlug` | Project creator. |

#### Live counters (`includeLiveStats` or `includeProjectDetails`)

Listing values can lag; these are read live and kept **next to** the listing values.

| Field | Meaning |
|---|---|
| `liveBackersCount`, `livePledged`, `livePercentFunded`, `liveState` | Current backers, pledged (project currency), % funded, state. |
| `liveCommentsCount` | Current comment count including replies. |
| `watchesCount` | People following ("remind me") the project. |
| `statsFetched` | `true` when the live counters were read. |

#### Details (`includeProjectDetails`)

| Field | Meaning |
|---|---|
| `rewards` | Reward tiers (see below). |
| `rewardTierCount` | Number of tiers in `rewards` (public tiers, or all tiers with `rewardTiers: all`). |
| `publicRewardTierCount` | With `rewardTiers: all`: how many of them are public. |
| `rewardTiersScope` | `public` or `all` — which tiers the row contains. |
| `addOnCount` | Number of add-ons. |
| `updatesCount` | Creator updates, including backer-only ones. `updatesCountExact` is `false` only for projects with 50+ timeline events (then it is computed from the total). |
| `commentsCount` | Comments including replies. `topLevelCommentsCount`: without replies. |
| `faqsCount` | Number of FAQ entries. |
| `projectWeLove` | Kickstarter "Projects We Love" badge (live value). |
| `creatorBackingsCount`, `creatorLaunchedProjects` | How many projects the creator has backed / launched. |
| `detailFetched`, `detailSource` | Details read (`graphql`, or `project_page` as fallback). |
| `detailError`, `statsError` | Why details / live counters are missing on this row (`null` when fine). |

**Each reward tier:** `rewardId`, `title`, `minimum` (minimum pledge, project
`currency`), `convertedMinimum` + `convertedCurrency` (USD), `description`,
`backersCount`, `available`, `limit`, `remaining`, `limitPerBacker`,
`estimatedDeliveryOn` (date), `shippingPreference`, `shippingSummary`,
`rewardType`; with `rewardTiers: all` also `isPublic`.

#### Bookkeeping (same on every row)

| Field | Meaning |
|---|---|
| `dataComplete` | `true` unless an enrichment you asked for is missing on this row (failed, or beyond `maxDetailsPerQuery`). |
| `_input` | The query that found this project (readable label). |
| `resultPage`, `resultRank` | Page and position in that query's results. |
| `_source`, `_scrapedAt`, `recordType` | Data source, scrape time (UTC), always `PROJECT` in the dataset. |
| `_error`, `_errorDetail` | Only on `ERROR` rows (`includeDiagnosticsInDataset`, or invalid input): error code and message. |

***

### Input

Give one query with the flat fields (`term`, `state`, `categoryId`, `woeId`,
`sort`, `staffPicks`, `raised`, `goal`) or several in `queries` — each object
takes the same keys plus `startPage`.

| Field | Notes |
|---|---|
| `term` | Free text. A term with no matches honestly returns 0. |
| `state` | `live`, `successful`, `failed`, `canceled`, `upcoming`. |
| `categoryId` | Parent ids: 1 Art, 3 Comics, 6 Dance, 7 Design, 9 Fashion, 10 Food, 11 Film & Video, 12 Games, 13 Journalism, 14 Music, 15 Photography, 16 Technology, 17 Theater, 18 Publishing, 26 Crafts — or any subcategory id. |
| `woeId` | Kickstarter location id (e.g. 2459115 = New York). |
| `sort` | `magic` (default), `popularity`, `newest`, `end_date`, `most_funded`, `most_backed`. |
| `raised` | 0 = under 75 % funded, 1 = 75–100 %, 2 = over 100 %. |
| `goal` | Goal band 0 (< $1,000) … 6 (> $100,000). |
| `maxProjectsPerQuery` | Default 60; 0 = up to Kickstarter's ceiling. |
| `includeProjectDetails`, `rewardTiers`, `includeLiveStats`, `maxDetailsPerQuery` | Enrichment (see fields and *Cost*). |
| `dedupeAcrossQueries`, `onlyNewProjects`, `historyStoreName`, `excludeProjectIds` | Duplicate control (see below). |
| `includeDiagnosticsInDataset` | Put summary/error rows in the dataset too (v0.1 style). |
| `proxyConfiguration` | Residential by default — keep it (see *Reliability*). |
| `maxConcurrency`, `minRequestInterval` | Default 2 in flight, 1 s pacing. Kickstarter drops connections under load. |
| `selfTest` | Ignore the queries and run the built-in health check instead. |

***

### Query limits — what Kickstarter does not tell you

1. **12 projects per page, at most 200 pages: 2,400 projects per query.**
   Page 201 is a real 404, however many hits Kickstarter reports (a
   "successful" query reports ~299,000). The report shows `totalHits`,
   `estimatedReachable` and `estimatedUnreachable` per query. **To get more,
   split the query**: by `categoryId` (one query per subcategory), by `state`,
   by `goal` band, by `raised` band or by `woeId`, keeping each slice under
   2,400 — and leave `dedupeAcrossQueries` on so overlapping slices never
   duplicate.
2. **A wrong `categoryId` or `woeId` is not an error upstream — it returns
   every project.** The actor detects this from the rows
   (`categoryFilterHonoured` / `locationFilterHonoured` in the report, and
   `categoryMatchesRequested` / `locationMatchesRequested` on rows).
3. **A subcategory is a loose filter, a parent category is strict.** Measured
   on 2026-10-10 over the first 48 projects of five subcategories: 62–90 %
   on-category (Video Games: 89.2 % over 120 projects in the accuracy test) —
   the first page is often 100 %, later pages are not. Every row tells you
   (`categoryMatchesRequested`), and the report gives `categoryMatchRate`
   over all fetched rows. Filter on the flag for a strict set.
4. **`state=suspended` and unknown `sort` values are refused.** Kickstarter
   would silently answer them with all projects / the default order.

### Continue without duplicates

- **Resume a query.** When a query stops at your `maxProjectsPerQuery` (or a
  run is cut short), `RUN_REPORT.resumeInput` holds the same query with
  `startPage` set to the next page. Run it as the next input: you get the
  following pages and none of the projects you already have. Verified in the
  test suite: page 1–2, then a resume from page 3, 0 overlapping projects.
- **Several queries in one run** never return the same project twice
  (`dedupeAcrossQueries`, default on; the report counts the removed ones).
- **Across runs** turn on `onlyNewProjects`: every delivered project id is
  remembered in a key-value store on your account (`historyStoreName`) and
  skipped next time — good for daily monitoring of new launches. Or pass ids
  you already have in `excludeProjectIds`.

***

### Cost — measured, all-in

This actor charges **per project row** plus a **$0.005 start fee**, and you
also pay **platform usage** (compute + Residential proxy). Measured on Apify
on 2026-10-10, default settings, at list prices:

| 1,000 projects | platform usage | total on FREE ($0.002/row) | total on GOLD+ ($0.001/row) |
|---|---|---|---|
| Discovery only | $0.017 | **$2.02** | **$1.02** |
| + details & live counters (public tiers) | $0.063 | **$2.07** | **$1.07** |
| + details, `rewardTiers: all` | $1.761 | **$3.77** | **$2.77** |
| v0.1 with details, for comparison | $1.623 | $3.64 | $2.64 |

- Summary and error rows are **not** charged — only projects (v0.1 charged
  its summary rows too).
- Retries are included in the figures above. A 1,000-project run with details
  takes about 9 minutes.
- Details went from ~0.18 GB to ~0.004 GB of Residential traffic per 1,000
  projects by switching from project pages to Kickstarter's GraphQL API
  (8 projects per request). Secret/VIP tiers still need the page, so
  `rewardTiers: all` is opt-in.

### Accuracy — tested

Same three queries run on 2026-10-10 on this actor's v0.1 and v0.2 and on the
most-used competing Kickstarter scraper in the Store, 120 projects each, then
checked against Kickstarter's own live `stats.json`:

| | v0.1 | v0.2 | competitor |
|---|---|---|---|
| Same project ids (per query) | 120/120 | 120/120 | 120/120 |
| State correct | 100 % | 100 % | 100 % |
| Parent category correct (Technology) | 100 % | 100 % | 100 % |
| Subcategory correct (Video Games) | 89.2 % | 89.2 % | 89.2 % |
| …and flagged per row | yes | yes | no |
| Backers / pledged identical across actors | 100 % | 100 % | 100 % |
| Backers & pledged vs live `stats.json` (45 random) | 100 % | 100 % | 100 % |
| Summaries / errors mixed into the dataset | yes | **no** | no |
| Tells you what is missing (report, resume) | partly | **yes** | no |

The listing data is the same everywhere because it is Kickstarter's own; the
difference is what you are told about it. Details were checked separately:
reward minimums and per-tier backers identical to the project page on 178/178
public tiers, update counts 24/24, comment counts 23/24 (one comment posted
in between). Full method and per-query numbers ship with the actor's source
(`tests/accuracy`).

### Reliability and how it is tested

- **Daily self-test** on the live actor: fixed queries, invariant checks
  (state, category, live counters) and a comparison with Kickstarter's own
  `stats.json` on a random sample. Results are appended to a
  [public health log](https://api.apify.com/v2/datasets/FQYVGpjIbKqRzoBSz/items?format=json\&clean=1).
- A 27-case regression suite runs before every release (filters dropped by
  Kickstarter, ceilings, resume, duplicates, failure handling, cost
  accounting).
- Kickstarter's Cloudflare decides which browser fingerprints pass, and that
  changes from day to day. The actor starts with the profiles that currently
  pass on Residential IPs and keeps score during the run, so a newly blocked
  profile stops being used after one refusal.
- **Use the Residential proxy** (default). Datacenter IPs are refused by
  Kickstarter on every fingerprint as of 2026-10-10.

### Changelog

**0.2 — 2026-10-10**

- Dataset holds only clean project rows; summaries, errors, completeness and
  cost moved to the `RUN_REPORT` record (the Console run list keeps the short
  "Actor succeeded with N results" line)
  (`includeDiagnosticsInDataset` restores the v0.1 layout). Summary rows are
  no longer charged.
- `RUN_REPORT.resumeInput` and per-query `startPage` to continue without
  duplicates; `dedupeAcrossQueries` (default on), `onlyNewProjects`,
  `excludeProjectIds`.
- Details and live counters through Kickstarter's GraphQL API, 8 projects per
  request: ~50x less Residential traffic, details for every project by default
  (`maxDetailsPerQuery` 0 = all). New fields: `watchesCount`, `faqsCount`,
  `topLevelCommentsCount`, `projectWeLove`, `creatorBackingsCount`,
  `creatorLaunchedProjects`, `livePercentFunded`, `dataComplete`.
- `rewardTierCount` now counts real tiers only (v0.1 also counted the
  "pledge without a reward" option). New `rewardTiers: all` adds secret / VIP /
  late-pledge tiers.
- Fixed: queries could fail with "still challenged after 5 attempts" — the TLS
  rotation started with profiles Kickstarter now blocks on Residential IPs.
  Safari-first order plus in-run profile scoring.
- `categoryMatchRate` over all fetched rows (the first-page rate understated
  how loose subcategories are).
- Daily self-test with a public health log.

**0.1 — 2026-08-27**

- First release: discovery API, filter-honesty checks, 2,400-row ceiling
  reporting, optional project-page details and stats.json live counters.

# Changelog

This Actor's version history is a separate document: https://apify.com/fetchfoundry-labs/kickstarter-scraper-cheapest/changelog.md

# Actor input Schema

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

One or more discovery queries. Each object accepts term, state, categoryId, woeId, sort, staffPicks, raised and goal. Use several queries to get past the 2,400-row-per-query ceiling -- split by category, state or location. Leave empty to use the flat fields below. Each object may also carry startPage (1-200) to continue a walk an earlier run stopped -- the run's RUN_REPORT.resumeInput gives you these objects ready to paste.

## `term` (type: `string`):

Free-text search, used when 'Queries' is empty. Verified honest: a term with no matches returns zero results, not the unfiltered baseline.

## `state` (type: `string`):

Only these five are real filter values. 'suspended' looks like a sixth and is not one -- Kickstarter answers it with every state at once, identically to a nonsense string, so this actor refuses it.

## `categoryId` (type: `integer`):

Kickstarter category id. Parent ids (1 Art, 3 Comics, 6 Dance, 7 Design, 9 Fashion, 10 Food, 11 Film & Video, 12 Games, 13 Journalism, 14 Music, 15 Photography, 16 Technology, 17 Theater, 18 Publishing, 26 Crafts) filter strictly. A SUBcategory id is a relevance filter upstream and mixes in neighbouring categories -- rows carry categoryMatchesRequested so you can tell.

## `woeId` (type: `integer`):

Kickstarter location id, e.g. 2459115 for New York, NY. An unknown id is NOT an error upstream -- it silently returns every project. Rows carry locationMatchesRequested and the summary says whether the filter was honoured.

## `sort` (type: `string`):

An unrecognised sort is not an error upstream -- it silently becomes 'magic', so the run looks sorted and is not. Only these six are accepted here.

## `staffPicks` (type: `boolean`):

Restrict to projects Kickstarter has featured.

## `raised` (type: `integer`):

Kickstarter's own band: 0 = under 75% funded, 1 = 75-100%, 2 = over 100%.

## `goal` (type: `integer`):

Kickstarter's own goal band, 0 (under $1,000) through 6 (over $100,000).

## `maxProjectsPerQuery` (type: `integer`):

Set 0 to walk to the ceiling. That ceiling is 2,400 rows (page 200); page 201 is a genuine HTTP 404, so a broad query cannot be fully collected in one go however many hits it reports.

## `includeProjectDetails` (type: `boolean`):

Adds reward tiers (title, minimum pledge, backers per tier, limits, remaining, estimated delivery, shipping), add-on count, update count, comment count (incl. replies), FAQ count, followers (watchesCount), Projects We Love flag and creator history. v0.2 fetches this through Kickstarter's own GraphQL API, 8 projects per request (~4 KB per project instead of a ~600 KB project page), so it is cheap enough to leave on.

## `rewardTiers` (type: `string`):

With details on: 'public' lists the tiers a visitor sees on the Rewards tab (via GraphQL). 'all' also reads the project page to add secret, VIP and late-pledge tiers -- in testing 79 of 257 tiers on 24 live tech projects were not public. Every row says which it contains (rewardTiersScope).

## `includeLiveStats` (type: `boolean`):

Up-to-the-minute backers, pledged, comments, followers and state for each project (discovery results are cached and can lag). Fetched in the same GraphQL requests as the details, 24 projects per request when details are off. Kept side by side with the discovery values as live\* fields.

## `maxDetailsPerQuery` (type: `integer`):

Cap on how many projects per query get details / live counters. 0 = all of them (default since v0.2, because enrichment is now ~4 KB per project). Rows beyond the cap carry dataComplete=false.

## `dedupeAcrossQueries` (type: `boolean`):

A project matched by two queries in the same run appears once (under the first query that found it). The RUN_REPORT counts how many were removed.

## `onlyNewProjects` (type: `boolean`):

Remember every project id this actor delivered (in a named key-value store on your account) and skip them in later runs -- for scheduled monitoring without duplicates.

## `historyStoreName` (type: `string`):

Key-value store used by 'Only projects not delivered by earlier runs'. Use a different name per project/campaign to keep histories apart.

## `excludeProjectIds` (type: `array`):

Kickstarter project ids (the projectId column) to leave out, e.g. ones you already have.

## `includeDiagnosticsInDataset` (type: `boolean`):

Off (default): the dataset holds only clean PROJECT rows; per-query summaries, errors, completeness and cost are in the run's RUN_REPORT key-value record. On: also append the v0.1-style SEARCH_SUMMARY and ERROR rows to the dataset (they carry recordType, so filter on recordType = PROJECT for the projects).

## `maxConcurrency` (type: `integer`):

Requests in flight at once across all queries. Kickstarter drops back-to-back connections under load, so this is deliberately low.

## `minRequestInterval` (type: `integer`):

Politeness pacing shared across all workers.

## `proxyConfiguration` (type: `object`):

Apify Residential proxy by default. Since early October 2026 Kickstarter's Cloudflare challenges every Apify datacenter IP and direct connection (verified on cloud runs: 0 rows without a proxy or with datacenter proxy, all rows with Residential). Only switch it off when running from a residential network yourself.

## `selfTest` (type: `boolean`):

Ignore the queries above and run the actor's built-in health check: two fixed queries, invariant checks (state, category, live counters) and a comparison with Kickstarter's own stats.json for a random sample. The result is appended to a 'kickstarter-scraper-health' dataset. The maintainer runs this daily; its public log is linked in the README.

## Actor input object example

```json
{
  "queries": [
    {
      "categoryId": 12,
      "state": "live"
    },
    {
      "categoryId": 16,
      "state": "live"
    }
  ],
  "term": "solar charger",
  "state": "",
  "sort": "magic",
  "staffPicks": false,
  "maxProjectsPerQuery": 60,
  "includeProjectDetails": false,
  "rewardTiers": "public",
  "includeLiveStats": false,
  "maxDetailsPerQuery": 0,
  "dedupeAcrossQueries": true,
  "onlyNewProjects": false,
  "historyStoreName": "kickstarter-seen-projects",
  "includeDiagnosticsInDataset": false,
  "maxConcurrency": 2,
  "minRequestInterval": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "selfTest": false
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "queries": [
        {
            "term": "board game",
            "state": "live",
            "sort": "most_funded"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchfoundry-labs/kickstarter-scraper-cheapest").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 = {
    "queries": [{
            "term": "board game",
            "state": "live",
            "sort": "most_funded",
        }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("fetchfoundry-labs/kickstarter-scraper-cheapest").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 '{
  "queries": [
    {
      "term": "board game",
      "state": "live",
      "sort": "most_funded"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call fetchfoundry-labs/kickstarter-scraper-cheapest --silent --output-dataset

```

## MCP server setup

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

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/NKEj2muxACwJMywcd/builds/5JpCFeo6vJXHPngKB/openapi.json
