# Rave Finder (`kat_raszka/rave-finder`) Actor

Find electronic music events around you (currently only in Czechia)

- **URL**: https://apify.com/kat\_raszka/rave-finder.md
- **Developed by:** [Kateřina Raszka](https://apify.com/kat_raszka) (community)
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Rave Finder

Finds upcoming **electronic music and DJ events** — techno, house, drum & bass, and
generally-electronic events that don't name a specific genre — near a Czech city, within a
radius you choose. Searches Resident Advisor, event aggregators (GoOut, DnB e-Heard,
ColosseumTicket, KdyKde, xTicket, KoncertyPraha, Rave.cz), club websites, and Facebook
Events.

**v1 scope: Czech Republic only.**

### How it works

1. Geocodes the city you give it (OpenStreetMap Nominatim, no API key needed).
2. Crawls several kinds of sources, in phases (see "Actor-run budget" below):
   - **Resident Advisor** — the primary source, and the only one that's simultaneously free,
     genre-tagged at the source, and geographically scoped by the API itself. RA's GraphQL
     API needs no authentication, so `src/crawlers/residentAdvisorCrawler.js` calls it
     directly rather than paying for a scraper Actor: no per-event cost, and it doesn't
     consume a concurrent-Actor-run slot. Verified live: one country-wide query returns ~114
     upcoming Czech events, 111 of them with real street addresses, carrying RA's own genre
     taxonomy ("Techno", "Progressive House", "Garage", "Electronica"…).
     RA areas are city/region level — Prague and Brno have their own, smaller towns like
     Ostrava and Frýdek-Místek don't — so this queries the country-wide "All Czech Republic"
     area and lets the radius filter do the geography.
   - **Aggregators and club sites** — both crawled directly with Crawlee (free, no Actor
     call) through the shared extractors in `src/extractors/`, in three tiers: schema.org
     JSON-LD where a site publishes it, then DOM event cards, then dated links.

     This replaced an earlier pipeline that ran each club site through
     [`apify/website-content-crawler`](https://apify.com/apify/website-content-crawler) and
     parsed the resulting Markdown. That version found **0 events across 13 sites** on two
     separate runs, and the sites were never the problem:

     - Markdown flattening destroyed the card structure tying a date to its title. rokac.cz
       has 9 upcoming events in plain server-rendered HTML; the line heuristic saw none.
     - Nothing walked *nested* JSON-LD, so barrak.cz's 67 fully-structured events were
       invisible — they hang off a `LocalBusiness` object's `events` property.
     - It cost a paid Actor call and a concurrent-run slot per site, with 30–150s of
       container startup each, for no return.

     Crawling directly is free, needs no concurrency slot, and covers all 13 sites in about
     1.5 seconds. The JS rendering the Actor charged for was never what was missing; these
     are ordinary server-rendered pages. Two per-source title rules
     (`locationSuffixMarker`, `cityFromHashPrefix`) parse the venue and town out of titles
     on the two national genre calendars, which is what makes their events placeable at all.
   - **Club sites (Maps-discovered)** — the seeded list only covers ~17 venues in a handful
     of cities, so it contributes nothing for a city outside that list. To cover any Czech
     city/radius, `src/crawlers/mapsDiscoveryCrawler.js` searches Google Maps
     ([`compass/crawler-google-places`](https://apify.com/compass/crawler-google-places),
     \~$1.50 per 1,000 places — billed through the normal Apify account, not x402) for
     club-like venues near the geocoded city center, then crawls each discovered venue's
     website the same way as the seeded list. Capped by `maxMapsVenues`; set to `0` to
     disable.
   - **Facebook venue pages** — the source that actually works for small towns, and **on by
     default** (`includeFacebookVenuePages`). Rather than searching, it reads the events tab
     of venues already established to be in range. Verified live against Rokáč: its own
     website yielded nothing under the free `cheerio` crawler, while
     `facebook.com/rokac.cz/upcoming_hosted_events` returned 15 events with dates *and*
     coordinates. The plain page URL returns one empty record — the `/upcoming_hosted_events`
     tab is the URL shape that works, and `startUrls` takes plain strings, not `{ url }`
     objects (the Actor calls `url.match()` on each entry and crashes otherwise).
     Cost scales with the number of nearby venues instead of with search noise.
     Because a venue page publishes the venue's *whole* programme, events inherit the venue's
     `trustedElectronic` (so a DJ night naming no genre survives) but are screened by a
     non-music filter — a live run otherwise surfaced a wine-and-burčák tasting. The filter
     drops tastings, workshops, yoga, tournaments and markets while deliberately keeping
     anything that might be a DJ night.
3. Classifies each event's genre(s) — see `src/genreClassifier.js`:
   - Structural tags win outright (e.g. DnB e-Heard's `forcedGenre: 'drum_and_bass'`).
   - A specific genre keyword (techno/house/drum & bass, CZ + EN, word-boundary matched —
     not a plain substring check, since e.g. "techno" as a substring would false-positive on
     "technologie"/"technické") wins next.
   - Otherwise, sources already known to be dedicated to electronic music
     (`trustedElectronic: true` in `seedSources.js` — Rave.cz, GoOut's electronic-music
     category, and the seed clubs branded as electronic-only venues) still keep the event,
     tagged generically as `electronic`. This is what makes a branded event like "Beats for
     Love Experience w/ KANINE" or a local party name survive even though its title names no
     genre — the source itself is the guarantee.
   - Everywhere else (general ticketing aggregators, mixed-programming clubs, Facebook venue
     pages), an event only survives if its own text carries a generic electronic/DJ signal.
     Strong signals ("dj", "DJane", "b2b", "dj set", "dubstep", "trance") count anywhere
     including the description; weak ones ("bass", "beat", "party", "disco", "afterparty")
     count only in the title, because in a rock band's description "bass" is a guitar and a
     metal gig advertises an afterparty just as readily. This is what keeps rock/jazz/theatre
     listings on those same sources from flooding the output.
4. Geocodes each venue and filters by distance from the city center — cached in the
   key-value store, except Maps-discovered venues and Resident Advisor events, which already
   carry their own coordinates or a street address.

   Club sites are also **distance-pre-filtered before being crawled**, not just after:
   every seeded venue has a known city, so geocoding that city (cached, free) rules out the
   ones that can't possibly be in range. Searching Návsí used to spend one Actor call each on
   Cross Club, Roxy, Ankali, MeetFactory, Lucerna and the rest of the Prague/Brno list — all
   \~350km away, every resulting event discarded by the radius filter anyway.
5. Filters by requested genres and date range, then dedupes events that show up on more
   than one source.
6. Pushes the results to the default dataset.

### Searching a specific date window

`dateRangeDays` can only ever mean "the next N days", which is the wrong shape for the most
common reason to run this — a trip. "I'm in Prague 12–15 September" isn't expressible at all:
you'd have to wait until the 12th and pass `3`.

`dateFrom` / `dateTo` override it, and either works on its own:

| Given | Window searched |
|---|---|
| `dateFrom` + `dateTo` | exactly that, both bounds inclusive |
| `dateFrom` only | that date, for `dateRangeDays` afterwards |
| `dateTo` only | today until that date |
| neither | today for `dateRangeDays` — the original behaviour, unchanged |

Bad input fails loudly rather than being silently misread: `12.9.2026` is rejected with the
expected format, and a `dateTo` before `dateFrom` is an error. A window entirely in the past
warns that every source here lists upcoming events only.

The window is resolved **once** and passed into the Resident Advisor GraphQL filter, rather
than RA deriving its own from `dateRangeDays` — two independent computations of the same thing
would have drifted the moment explicit dates existed. Bounds stay as plain `YYYY-MM-DD`
strings compared lexicographically, which avoids the timezone bugs that come from turning
"the 12th" into an instant.

This is also where Resident Advisor earns its place. It's Prague-centric (105 of ~115 Czech
events), so it contributes nothing for a village search — but for a Prague trip it's the best
source here, with real street addresses and RA's own genre taxonomy. A live run for
`Praha` / 15km / 12–15 Sept returned 17 events across Cross Club, Roxy, Ankali, MeetFactory
and a dozen others. Scoping the window also shrinks the RA request itself: 24 events fetched
instead of 137.

### Adding a promoter page

Around small towns the **promoter** creates the event and only tags the venue, so a venue's
own Facebook page often won't list it. A live example: a Jablunkov event the Actor missed was
nowhere on Rock Café's page (its Upcoming started the following day and its Past stopped two
weeks earlier), nowhere in any wired source, and not indexed by DuckDuckGo or Bing. Facebook's
own search is the only place it exists, and that needs a login this Actor doesn't have.

So `facebookPages` lets you close that gap yourself, without a code change or redeploy: paste
the promoter's page URL into the Actor input and it's scraped alongside the built-in venues.

- Either shape works — a page URL (`https://www.facebook.com/somepromoter`) gets the events
  tab appended automatically, and a single event URL (`https://www.facebook.com/events/123/`)
  is passed through untouched. Trailing slashes, query strings and stray whitespace are fine.
- Entries that aren't facebook.com URLs are skipped with a warning rather than wasting a call.
- These pages deliberately skip the city pre-filter, since the point is to reach a promoter
  whose town the Actor can't guess. Nothing is lost by that: Facebook returns real venue
  coordinates, so the per-event radius filter still decides what's actually nearby.
- A page already covered by the seed list is deduplicated, so adding one you already have
  costs nothing.
- Each page costs one Actor call and shares the `maxFacebookEvents` budget.

**Same-day events are a blind spot.** Facebook moves an event out of "Upcoming" once it has
started, so an evening run won't see anything happening that same evening — the scraper reads
the upcoming tab only. Run in the morning for same-day coverage.

### Why Facebook's event search was removed

Facebook's event search is keyword matching, not a location filter, so the place name in a
query is advisory at best. It was tried three ways and each failed differently:

- **The literal input city ignored the place.** "drum and bass Návsí" returned ~150 global
  D\&B events from Coventry, Budapest and Brooklyn.
- **Real nearby towns ignored the genre.** "drum and bass Havířov" returned every unrelated
  event in Havířov — maternity-ward tours, yoga classes, a dog-school race, board-game nights.
- **Enabling it aborts the whole run.** This is what settled it. A Návsí search with
  `includeFacebookEvents: true` spent the entire 300-second budget inside the scraper and was
  killed by the platform, so **nothing was pushed at all** — every other source's results
  were discarded too. It also drew Facebook's own "Rate limit exceeded" and looped on
  pagination retries ("Forcing additional scrolling", retryCount past 10).

That run's harvest was three distinct kinds of garbage, none of it near Návsí:

- **Jazz rhythm sections**, because the search ORs the words. "drum and bass Karviná" kept
  returning "Joan Minor featuring The Uli Geissendoerfer Trio with Peppe on Drums and Derek
  Jones on the Bass" — seven times over, under seven different event ids.
- **The town's unrelated events**: a political-party barbecue (GRILOVAČKA ČSSD KARVINÁ), a
  kids' craft workshop (Lapač slunce), an open day, a photography course.
- **Real D\&B on the wrong continent**: Budapest Park, Poland, the US.

Every one of those is billed at ~$0.013 *before* this Actor's genre and radius filters throw
it away. A default-off switch wasn't enough protection, since flipping it guarantees a lost
run, so the path is deleted — recoverable from git history if a different approach ever
warrants it.

None of this is a limitation of Facebook *as a source*: small promoters genuinely do publish
there, just on venue and promoter **pages** rather than anywhere the event search reaches.
That's what `crawlFacebookVenuePages` does — it asks named, already-in-range venues what they
have on, so cost scales with the number of nearby venues instead of with search noise.

### Icon

The Actor's icon lives at `.actor/logo.png` (512x512 PNG) so it's versioned with the project,
but **Apify does not pick it up from the repo** — `actor.json` has no icon property, and the
build ignores the file. It has to be uploaded once in Apify Console under
**Publication → Display information → Actor logo**. Re-uploading is only needed when the
image itself changes; it survives every rebuild.

### Actor-run budget

Apify caps **concurrent Actor runs per account** (5 on the plan this was built against,
*including this Actor's own run*), and a called Actor's default memory counts against a
shared ceiling too. Both limits were hit hard in testing, and failed silently-ish: every
club-site crawl in a run aborted with "you will exceed your limit of 5 concurrent Actor runs"
while the run itself still reported success and zero results.

Moving the club and aggregator crawls in-process removed most of that pressure — they now
cost zero Actor slots. What remains is phased rather than maximally parallel:

| Phase | What runs | Actor calls |
|---|---|---|
| 1 | Resident Advisor, aggregators, nearby-town lookup, Maps discovery | 1 (Maps only) |
| 2 | Club sites (in-process), then Facebook venue pages | 1 per venue page, 3 at a time |

Facebook venue pages take **one Actor call per page**. Batching them into a single call looks
cheaper but isn't: the scraper's `maxEvents` is a whole-run total, so whichever page it
crawls first swallows the entire budget — a run with 6 pages and a cap of 20 returned 16
events all from one venue, and the other five contributed nothing. One call per page with
`maxEvents / pages` each costs the same and actually covers every venue.

Free/keyless sources (Resident Advisor, Nominatim, Overpass) and the in-process Crawlee
crawls cost **zero** Actor slots.

**A run that overshoots the timeout publishes nothing.** Everything is pushed in one call at
the end, so an abort doesn't degrade the output, it destroys it — including work every other
source already finished. Geocoding is the only step that can still get there (1 request/second
under Nominatim's policy, and a failed lookup is retried three times with backoff — a Návsí
run had 17 unplaceable venues at roughly 6s each), so it stops starting new lookups 45s before
the assumed 300s limit and publishes what it placed, logging a warning. A normal run finishes
in about 130s locally, so this is a safety net rather than something you should hit; because
successful lookups are cached in the key-value store, a re-run gets further.

**The 300-second default run timeout is the design constraint, not a problem to raise.**
Note that `defaultRunOptions` is *not* a valid `actor.json` property (see Apify's actor.json
reference) — putting it there is silently ignored, which cost one early run an abort. It can
only be changed in Apify Console under Settings → Run options. Since the paid per-site crawls
are gone, a full run finishes well inside the default, and the earlier deadline-and-skip
machinery has been removed. The remaining slow step is geocoding, rate-limited to 1 request
per second by Nominatim's usage policy — which is why events are filtered by city first
(geocoding each distinct town once, cached) before any venue is geocoded individually.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `city` | string | *(required)* | Czech city to search near, e.g. `"Brno"`. |
| `radiusKm` | integer | `30` | Max distance from the city center, in km (1–300). |
| `genres` | array | all four | `techno`, `house`, `drum_and_bass`, `electronic` (generic — electronic/DJ events with no specific genre named). |
| `dateRangeDays` | integer | `30` | Only include events within this many days from now (1–180). |
| `dateFrom` | string | *(none)* | Start of an explicit window, `YYYY-MM-DD`. Overrides `dateRangeDays` — see below. |
| `dateTo` | string | *(none)* | End of the window, `YYYY-MM-DD`, inclusive. |
| `includeFacebookVenuePages` | boolean | `true` | Ask in-range venues' Facebook pages what they have on. The only Facebook path — the event search was removed, see below. |
| `facebookPages` | array | `[]` | Extra Facebook page or event URLs to check alongside the built-in venues. See below. |
| `maxFacebookEvents` | integer | `20` | Total budget for Facebook venue-page events, split evenly across the pages asked, to control cost. |
| `maxMapsVenues` | integer | `5` | Caps Maps-discovered venues per search term, to control cost; `0` disables Maps discovery. Low by default — each discovered venue costs an Actor call, and most Maps hits are dance schools and bars, not electronic venues. |

See `.actor/input_schema.json` for the full schema.

### Running locally

```bash
npm install
apify run
```

(Or `npm start`, which just runs `node src/main.js` directly — `apify run` additionally
sets up local Actor storage under `./storage`, which is the more realistic way to test.)

Provide input either by editing `storage/key_value_stores/default/INPUT.json` after a
first `apify run`, or by running `apify run --input '{"city": "Brno"}'` (see the
[Apify CLI docs](https://docs.apify.com/cli) for details).

### Project structure

```
.actor/actor.json          Actor metadata
.actor/logo.png            Store/Console icon, 512x512 (upload via Console — see below)
.actor/input_schema.json   Input schema
src/main.js                Pipeline orchestration
src/sources/seedSources.js Seed list of club sites and aggregators
src/crawlers/               One crawler module per source type
src/extractors/dates.js     Czech date formats (ISO, numeric, named month, year-less)
src/extractors/jsonLdEvents.js  Recursive schema.org Event extraction, any nesting depth
src/extractors/eventCards.js    DOM event-card extraction, for pages without JSON-LD
src/concurrency.js          Bounded-concurrency helper for crawling many sites in parallel
src/geocode.js              Nominatim geocoding + Haversine distance, KV-cached
src/genreClassifier.js      Genre classification: specific keywords, trusted-source fallback
src/dedupe.js               Cross-source duplicate detection
Dockerfile                  apify/actor-node base image
```

### Known limitations (v1)

- Non-JSON-LD extraction is generic (event cards keyed off a date, plus a dated-link
  fallback), not a per-site scraper, so it will miss events on unusually-structured pages.
  This is an accepted trade-off rather than maintaining ~20 bespoke scrapers, but it does
  fail visibly on some sites: dnbczevents.cz groups listings under day headers, so every
  event resolves to the weekday above it ("pátek", "sobota"). Dates are read correctly; only
  the titles need a site-specific selector.
- Dates written without a year are assumed to be the **current** year, which means a
  genuinely next-January event listed as "9. 1." will be dated this January and dropped.
  Deliberate: the alternative (rolling past months forward) turned dnbeheard.cz's 501
  entries into a wall of January-2027 parties that don't exist. A miss is a gap in coverage;
  a phantom is wrong data in the output.
- Genre/electronic-music detection is keyword-based for anything not from a
  `trustedElectronic` source. The vocabulary covers genre names, lineup notation (`w/`, `b2b`,
  `dj set`, `DJane`), the bass/beat/trance word family, and a short list of named brands
  (Beats for Love, Let It Roll). A listing that names none of those — just a bare artist name
  — is still missed. The gate is tested against 29 real titles from these sources: all 10
  electronic ones match and all 19 rock/metal/community ones don't ("Live Tribute Act To
  RAMMSTEIN", "Moravský ples", "Taneční kurz pro dospělé").
- Scope is Czech Republic only.
- **The two national D\&B/techno calendars are now the highest-yield sources**, both read by
  the card extractor with a small per-source title rule:
  [jiripetrak.cz](https://www.jiripetrak.cz/cs/drum-a-bass-a-techno-parties-kalendar-akci-44/)
  (143 upcoming events, all carrying "▼ Venue, Town") and
  [dnbeheard.cz](https://dnbeheard.cz/kalendar-akci) (a full year at ~501, written
  "#Town Title, Venue", 500 of which parse to a town). Only
  [dnbczevents.cz](https://dnbczevents.cz/akce.php) remains parked in
  `CANDIDATE_AGGREGATORS_NEEDING_CUSTOM_EXTRACTION`.
- **Resident Advisor doesn't help outside Prague/Brno.** Its Czech coverage is
  Prague-centric: of ~115 upcoming events, 105 are Prague, 3 Brno. For a search near a
  smaller town it contributes candidates but no results — the nearest listing to Návsí was
  Olomouc, ~100km out. Excellent for Prague or Brno searches, irrelevant for a village.
- **GoOut is underused.** It's the largest Czech event source, and it does have a real public
  API — `https://goout.net/services/entities/v1/schedules` responds with structured schedules
  (dates, pricing, ticketing state) once you pass the required `languages[]` parameter. But
  its filter parameter names still need reverse-engineering: `tag` and `city` were both
  silently ignored in testing, and venue/performer data appears to need an `include=`
  parameter. Until that's worked out, GoOut is still just being HTML-scraped like the other
  aggregators. Worth doing — it's probably the biggest remaining coverage win.
- **A Facebook venue page is only worth seeding once it's confirmed to host events, and most
  don't.** Four researched-but-unvalidated pages (TESLA Production/Třinec, PartyTime and
  Project Bar, DNB pro Ostravaky) were seeded and every one returned "No event detail URLs
  found". The slugs were all real; the pages simply host nothing. Checking two by hand showed
  why, and it's a regional pattern: TESLA's events tab reads "No events to show" with only
  past entries, and PartyTime's page has no events tab at all. Around here the **promoter**
  creates the event and merely tags the venue — TESLA's own feed advertises "Future Control
  Open Air 2026", hosted by a separate Future Control page. Promoter pages are the better
  target, but finding them needs a logged-in Facebook search this Actor can't perform, so
  they have to be added by hand.
- **Slovak and Polish events near the eastern border are dropped**, because scope is
  Czech-only and every place name is geocoded with ", Czech Republic" appended. That's a real
  gap for a border town rather than a theoretical one: from Návsí, Žilina (SK) is 40km and
  Cieszyn (PL) 20km, while Prague is 316km — and dnbeheard.cz does list Žilina D\&B nights.
  They fail to geocode and are discarded as unplaceable.
- **Facebook coverage is only as good as the hand-maintained venue list.** With the event
  search gone (see above), the only Facebook path is asking named venue pages what they have
  on — and finding the right pages needs a logged-in Facebook search this Actor can't do. In
  the Návsí region the promoter creates the event and merely tags the venue, so promoter
  pages are the better target and have to be added by hand.

# Actor input Schema

## `city` (type: `string`):

Czech city to search near, e.g. "Brno".

## `radiusKm` (type: `integer`):

Maximum distance from the city center to include events, in kilometers.

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

Electronic music genres to include. "Electronic (general)" covers events that are clearly electronic/DJ music but don't name a specific genre in their own listing (e.g. a branded party or festival satellite event) — kept when the source itself is dedicated to electronic music.

## `dateRangeDays` (type: `integer`):

Only include events happening within this many days from now.

## `dateFrom` (type: `string`):

Start of the window to search, as YYYY-MM-DD. Use this with To date for a trip — "I'm in Prague 12-15 September" is dateFrom 2026-09-12, dateTo 2026-09-15. Leave both empty to search the next few days instead, per Date range (days).

## `dateTo` (type: `string`):

End of the window to search, as YYYY-MM-DD. Inclusive.

## `includeFacebookVenuePages` (type: `boolean`):

Fetch events directly from the Facebook pages of venues already found to be in range, instead of using Facebook's event search. On by default: verified live that a venue page's events tab returns real dated events with coordinates, and cost scales with the number of nearby venues rather than with search noise. Shares the maxFacebookEvents cap.

## `facebookPages` (type: `array`):

Facebook page or event URLs to check in addition to the built-in venues. Use this when you know a local promoter or venue the Actor doesn't: around small towns the promoter creates the event and only tags the venue, so the venue's own page won't list it. Paste either a page URL (https://www.facebook.com/somepromoter) or a single event URL — both work. Each page costs one Actor call and shares the maxFacebookEvents budget.

## `maxFacebookEvents` (type: `integer`):

Caps how many Facebook events are fetched, to control cost (apify/facebook-events-scraper runs at roughly $0.013 per event). Kept low: Facebook's event search OR-matches, so a query like "drum and bass Havířov" also returns every unrelated event in that town — yoga classes, board-game nights — each of which costs the same per-event fee before this Actor's genre filter discards it.

## `maxMapsVenues` (type: `integer`):

Discovers club-like venues near the city via Google Maps (compass/crawler-google-places, ~$1.50 per 1,000 places), so cities outside the curated seed list still get coverage. Caps results per search term; set to 0 to disable this source entirely. Kept low by default: each discovered venue costs an Actor call to crawl, and in practice most results are dance schools, restaurants and bars rather than electronic venues.

## Actor input object example

```json
{
  "city": "Brno",
  "radiusKm": 30,
  "genres": [
    "techno",
    "house",
    "drum_and_bass",
    "electronic"
  ],
  "dateRangeDays": 30,
  "includeFacebookVenuePages": true,
  "facebookPages": [],
  "maxFacebookEvents": 20,
  "maxMapsVenues": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing every matching event found

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

Key-value store holding the geocode cache

# 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 = {
    "city": "Brno",
    "facebookPages": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("kat_raszka/rave-finder").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 = {
    "city": "Brno",
    "facebookPages": [],
}

# Run the Actor and wait for it to finish
run = client.actor("kat_raszka/rave-finder").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 '{
  "city": "Brno",
  "facebookPages": []
}' |
apify call kat_raszka/rave-finder --silent --output-dataset

```

## MCP server setup

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

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/bDKfDCjrs2Yl9LlM5/builds/S4bsTdmB8bTSeeDqP/openapi.json
