# Event Scraper: Resident Advisor, Luma, Eventbrite & DICE (`fkhhb/eventcollator`) Actor

Scrape live events from Resident Advisor, Luma, Eventbrite and DICE with one Actor, in one normalized schema. Pick any combination of sources per run instead of running four separate scrapers with four different output shapes. Filter by date, genre, and price.

- **URL**: https://apify.com/fkhhb/eventcollator.md
- **Developed by:** [Friedrich Bremer](https://apify.com/fkhhb) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 resident advisor events

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Event Collator

**One Actor for four live-events sources - Resident Advisor, Luma, Eventbrite
and DICE - returning ONE consistent event schema no matter which platform(s)
you pulled from.** Every other events scraper on Apify covers exactly one
platform, with its own field names and its own gaps. This Actor's actual
job is collation: pick any combination of sources in a single run, get one
dataset back, in one shape, ready to merge/dedupe/filter without writing a
per-platform adapter first.

- **Genuinely live, public data only.** No login, no scraping behind a
  paywall, no API keys to manage. Every field below was verified against
  real, current listings - not inferred from documentation, because two of
  these four platforms (Luma, DICE) don't publish one for the endpoints
  this Actor uses.
- **One normalized event object.** Same top-level shape from every source:
  `title`, `description`, `startTime`, `endTime`, `venue`, `url`,
  `imageUrl`, `organizer`, `lineup`, `genres`, `price`. The platform's full
  original response is still there too, under `raw`, for anyone who wants
  it - normalization never throws data away.
- **Multi-source in one run.** Set `sources` to any combination; results
  interleave into one dataset item stream, filterable by date, genre/tag,
  and free-only identically regardless of source.

### What this is good for

Event aggregation for an app or website, local/nightlife discovery
products, music-industry market research, city/venue analytics, building
your own "what's on this weekend" feed, monitoring a specific genre or
scene across multiple platforms at once.

### What this is NOT

Not a lead-generation tool (no organizer email/phone enrichment, no CRM
export) - several competing actors add that on top of the same underlying
scrape, at a higher price, and if that is genuinely what you need, one of
those is the better fit. Not a ticketing/purchasing integration. Not a
general-purpose web scraper - it only understands these four sites'
specific data shapes.

### Sources - what's possible, and what genuinely isn't

Read this section before you run anything. Every limit below is a **real,
verified constraint of the source platform itself**, not a shortcut this
Actor took - and knowing them up front is the difference between "this
Actor is broken" and "this city just doesn't have Luma coverage."

#### Resident Advisor (ra.co)

Electronic music / club culture - the richest data of the four sources:
full description, venue with coordinates, complete artist lineup, genre
tags, promoter name, front+back flyer images, ticket prices and
ticketed status.

- **Coverage:** any `countryCode/areaUrlName` ra.co itself uses in its own
  URLs (`ra.co/events/de/berlin` -> `"de/berlin"`). Hundreds of areas
  worldwide - RA is a global platform, not hub-city-limited like Luma/DICE.
- **Date filtering is real and server-side** (`dateFrom`/`dateTo`).
- **Verified hard limit: request size.** RA's own API silently returns
  **zero events** - not an error, just an empty page - for a page size
  above roughly 100-150. This Actor clamps every request to 100
  regardless of what `maxItemsPerSource` you set, specifically so you can
  never hit this and get a confusing empty result. This was found by
  testing, not assumed: 100 returns real results, 150 and 250 both
  silently return nothing, repeatably.
- Most cities' real RA supply per day is well under 100 events anyway, so
  in practice this ceiling rarely matters - it exists for the handful of
  very active areas where it would otherwise bite.

#### Luma (lu.ma)

General-purpose events - tech meetups, conferences, community/social
events, workshops. Broader subject matter than the other three, which
skew nightlife/music.

- **Coverage: hub cities only.** Luma has a curated "discover" page for
  its own list of cities, not every city worldwide - a city with no
  discover page returns 0 events (Actor logs this clearly, it's not
  silent). Check `lu.ma/discover` for Luma's current hub list before
  assuming a smaller city will return anything.
- **Full description is opt-in** (`enrichEventDetails: true`) - one extra
  request per event for the plain-text description and real category
  tags. Off by default because it roughly doubles run time.
- **No native date-range filter on Luma's own API** - this Actor fetches
  the discover feed and applies `dateFrom`/`dateTo` client-side.
- Price/ticket data is present when Luma exposes it, `null` otherwise -
  never guessed.

#### Eventbrite

The broadest event-type coverage of the four - anything from club nights
to markets to professional conferences.

- **Requires a residential proxy** - Eventbrite's city pages sit behind
  AWS WAF bot protection; a plain datacenter proxy gets served a "Human
  Verification" page instead of real content. This Actor requests Apify's
  `RESIDENTIAL` proxy group automatically for this source. Residential
  bandwidth costs more than datacenter, which is why Eventbrite is priced
  higher per event than the other three sources below.
- **Honest, real gap: ticket price coverage is partial.** The listing page
  loads every event in one request (fast, no pagination needed), but
  price is rendered per-card in the DOM and only cards Eventbrite has
  actually hydrated at query time expose one. In practice this resolves
  price for roughly a quarter to a third of paid events - the rest come
  back with `price.isFree: null` (genuinely unknown), never a guessed
  value. If ticket-price completeness is your primary need, this source
  will under-deliver on it; the other three fields (title, date, venue,
  description, tags) are complete for every event either way.
- **No native date-range filter on Eventbrite's listing page** - applied
  client-side, same as Luma.

#### DICE (dice.fm)

Live music / club nights / festivals.

- **Verified hard limit: hub cities only, AND no public city-name search.**
  DICE's own `api.dice.fm/cities` endpoint ignores every query parameter
  tried and only ever returns DICE's own curated hub-city list - there is
  no way to resolve an arbitrary city name to DICE's internal city id.
  This Actor ships a small, hand-verified table of known city ids; if
  yours isn't in it, open `dice.fm/browse/<your-city>-<id>` yourself and
  pass the id straight through via `diceCityId` - this is a real
  workaround, not a placeholder.
- **No pagination past the default browse page.** DICE's city page is a
  fixed-size "Popular Events" view (commonly ~30 events) with no
  cursor/pagination mechanism this Actor could drive further. A
  `maxItemsPerSource` above that just returns everything the page has -
  it can't manufacture more.
- **End time is estimated, and the output says so.** DICE's listing view
  never includes an end time at all; this Actor assumes a 4-hour duration
  and sets `endTimeIsEstimated: true` on every DICE row so you can tell
  the difference between a real and an assumed value.
- No residential proxy needed (same server-rendered-JSON pattern as
  Eventbrite's listing page, without the WAF wall).

### Output schema

Every dataset item has this shape, regardless of source (fields that
genuinely don't apply to a source, e.g. `lineup` for Eventbrite, are
present as an empty array/`null`, never omitted):

```json
{
  "sourcePlatform": "resident_advisor | luma | eventbrite | dice",
  "sourceEventId": "string",
  "title": "string",
  "description": "string | null",
  "startTime": "ISO 8601 in UTC, e.g. 2026-09-24T20:00:00.000Z | null",
  "endTime": "ISO 8601 in UTC | null",
  "timezone": "IANA timezone of the event, e.g. Europe/Berlin | null",
  "venue": { "name": "string | null", "address": "string | null", "lat": "number | null", "lng": "number | null" },
  "url": "string | null",
  "imageUrl": "string | null",
  "images": ["string", "..."],
  "organizer": { "name": "string", "url": "string | null" },
  "lineup": ["artist/guest name", "..."],
  "genres": ["genre or tag", "..."],
  "price": { "isFree": "boolean | null", "amount": "number | null", "currency": "string | null" },
  "isTicketed": "boolean",
  "minimumAge": "number | null",
  "raw": { "...the platform's original response for this event, unmodified" }
}
```

**Times are always UTC.** The four platforms each emit times differently
(local time with no offset, UTC, or an explicit offset); this Actor converts
every `startTime`/`endTime` to UTC ISO 8601 using the event's own timezone,
so rows from different sources sort and compare correctly. Use `timezone`
to display local time. Coordinates are always numbers.

### Input reference

| Field | Applies to | Notes |
|---|---|---|
| `sources` | all | Array; omit to run all four. |
| `city` | Luma, Eventbrite, DICE | Platform's own city slug/name. |
| `country` | Eventbrite | Required if Eventbrite is selected. |
| `areas` | Resident Advisor | `["countryCode/areaUrlName", ...]`, required if RA is selected. |
| `diceCityId` | DICE | Direct city-id override for cities outside the built-in table. |
| `dateFrom` / `dateTo` | all | Native + server-side for RA; client-side filter for the other three. |
| `maxItemsPerSource` | all | Per source (per area, for RA). RA is internally clamped to 100 regardless. |
| `enrichEventDetails` | Luma | Full description + real tags, one extra request/event. |
| `genreFilter` | all | Case-insensitive substring match against `genres` + `lineup`. |
| `freeOnly` | all | Keeps only rows resolved as free; unknown price is excluded, not assumed free. |
| `proxyConfiguration` | Eventbrite | Defaults to Apify's `RESIDENTIAL` group automatically. |

### Pricing

Pay-per-event, charged only for what actually lands in the dataset - a
city with no coverage for a source (an unresolvable Luma city, an
unconfigured DICE city) costs nothing for that source. One run can cover
all four platforms, so there is no need to pay for, run and merge four
separate single-platform scrapers.

| Source | Price |
|---|---:|
| Resident Advisor | **$0.0024 / event** |
| Luma | **$0.0016 / event** |
| Eventbrite | **$0.0024 / event** |
| DICE | **$0.0020 / event** |
| Actor start | $0.00005 (standard platform charge) |

### Roadmap (not built yet - stated honestly, not implied)

- Direct event-URL scraping (paste a single event link instead of
  searching by city/area) for all four sources.
- Server-side genre filtering for Resident Advisor (RA's GraphQL API
  supports it; not wired into this Actor's input yet).
- A `monitorMode` that only returns events new since the last run.

None of the above exists today - everything in the "Sources" section above
is what actually ships.

# Actor input Schema

## `sources` (type: `array`):

Which platform(s) to scrape in this run. Leave empty to run all four. Selecting more than one merges their results into a single normalized dataset.

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

Used by Luma, Eventbrite and DICE. Luma: the lu.ma/<slug> city slug (e.g. "berlin") - only works for Luma's own curated hub cities. Eventbrite: the Eventbrite city name (e.g. "berlin"), any city Eventbrite itself covers. DICE: a city name from the small known-hub-cities table in src/sources/dice.js (e.g. "Berlin", "Munich", "Hamburg") - if your city isn't in that table, use `diceCityId` instead. Not used for Resident Advisor - use `areas`.

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

Eventbrite only, required if "eventbrite" is in `sources`: the country name as Eventbrite's own URLs spell it (e.g. "germany", "united-states").

## `areas` (type: `array`):

Required if "resident\_advisor" is in `sources`: areas in countryCode/areaUrlName format, e.g. "de/berlin" - the exact format ra.co itself uses in its own URLs (ra.co/events/de/berlin). You can list several to pull multiple areas in one run.

## `diceCityId` (type: `string`):

DICE's internal city id has no public name-search endpoint, so only a small hand-verified set of cities work via `city` above. If yours isn't in that set, open dice.fm/browse/<your-city>-<id> in a browser and paste the id here (the number after the last "-" in the URL) to scrape any DICE hub city directly.

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

Only return events starting on or after this date/time (ISO 8601, e.g. "2026-10-01"). Applied server-side for Resident Advisor; applied to every source's already-fetched results otherwise (see the README - not every source's API supports a native date filter).

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

Only return events starting on or before this date/time (ISO 8601). Same filtering behavior as `dateFrom`.

## `maxItemsPerSource` (type: `integer`):

Maximum number of events to return from EACH selected source (for Resident Advisor, per area). Resident Advisor is internally capped at 100 regardless of this value - a real, verified limit of its own API, not a limitation of this Actor (see the README). DICE is capped by that city's own default browse-page size (commonly ~30) - this only ever truncates, it can't make DICE paginate further.

## `enrichEventDetails` (type: `boolean`):

Fetch each Luma event's detail page for a full plain-text description and real category tags. One extra request per event - slower, and only affects Luma.

## `genreFilter` (type: `array`):

Optional. Only keep events whose genres, tags, or lineup contain at least one of these terms (case-insensitive substring match), e.g. \["techno", "house"]. Applied client-side across all sources after fetching, since not every source's API supports server-side genre filtering.

## `freeOnly` (type: `boolean`):

Only keep events resolved as free (price.isFree === true). Because price coverage is incomplete for some sources (see the README, especially Eventbrite), this can under-return - an event with unknown price is excluded, not assumed free.

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

Only used for the Eventbrite source, which requires a residential-capable proxy to get past its bot-protection wall (see the README). Leave empty to use Apify's RESIDENTIAL proxy group automatically. Resident Advisor, Luma and DICE do not need a residential proxy and ignore this field.

## Actor input object example

```json
{
  "sources": [
    "resident_advisor",
    "luma",
    "eventbrite",
    "dice"
  ],
  "city": "berlin",
  "country": "germany",
  "areas": [
    "de/berlin"
  ],
  "maxItemsPerSource": 50,
  "enrichEventDetails": false,
  "freeOnly": false
}
```

# Actor output Schema

## `events` (type: `string`):

Every event pushed by this run, in the unified schema: sourcePlatform, title, startTime, venue, price, lineup, genres, url, plus the platform's original response under raw.

# 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": "berlin",
    "country": "germany",
    "areas": [
        "de/berlin"
    ],
    "maxItemsPerSource": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("fkhhb/eventcollator").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": "berlin",
    "country": "germany",
    "areas": ["de/berlin"],
    "maxItemsPerSource": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("fkhhb/eventcollator").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": "berlin",
  "country": "germany",
  "areas": [
    "de/berlin"
  ],
  "maxItemsPerSource": 50
}' |
apify call fkhhb/eventcollator --silent --output-dataset

```

## MCP server setup

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

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/KcIo2Olc1FdlKiqy2/builds/l6xkKUiKyjavfYuE9/openapi.json
