# Events Scraper (Eventbrite + Luma) (`signalclear/events-scraper`) Actor

Public events from Eventbrite and Luma in one run: title, dates with time zone, venue, coordinates, organizer, price, tickets, categories and Luma guest counts. Filter by city, keyword, dates, free or online. Bounded retries; failed queries are never charged.

- **URL**: https://apify.com/signalclear/events-scraper.md
- **Developed by:** [signal clear](https://apify.com/signalclear) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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.

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

## Events Scraper (Eventbrite + Luma)

Collect public event listings from **Eventbrite** and **Luma** in one run. Give it cities and optional keywords, pick a date window, and get one clean, de-duplicated table of events from both platforms: title, start and end time with time zone, venue and coordinates, organizer, price range, ticket link, image, categories, and Luma's guest counts.

Useful for local event guides, event-promoter and venue lead lists, market research, and seasonal sweeps: holiday markets, New Year's Eve parties, winter conferences and meetups.

### What it does

- **Two platforms, one schema.** Every row has the same fields whichever platform it came from, plus a `source` column.
- **Bounded.** Every request has a timeout, every query has a fixed number of attempts (each on a fresh proxy session), and each query stops at your per-query limit. The run always ends.
- **Failed queries are never charged.** A query (one platform x one location x one keyword) is saved only once it has completely succeeded. If it still fails after its retries, nothing from it reaches the dataset. It is listed in the `FAILED_QUERIES` record of the run's key-value store instead, with the reason. The run fails only if every query failed.
- **De-duplicated.** An event that turns up in several of your queries (say, two overlapping keywords) is saved once per run.
- **No login, no API keys.** It reads the same public listings a visitor sees.

### Input

| Field | What it does |
|---|---|
| `sources` | `both` (default), `eventbrite` or `luma`. |
| `locations` | Cities, one per line: `Austin, TX`, `London, United Kingdom`, `Berlin`. Eventbrite also accepts its own place slug (`tx--austin`). Luma covers its discover cities (about 90 large cities worldwide). For any other place on Luma, enter `latitude,longitude`, such as `45.5152,-122.6784`. |
| `keywords` | Optional search terms, one per line, such as `christmas market`, `new year's eve` or `conference`. Each keyword is its own query in every location. Leave empty for all events. |
| `dateRange` | `any` (default), `today`, `tomorrow`, `this-weekend` (Fri-Sun), `this-week` (to Sunday), `next-week` (Mon-Sun), `this-month`, or `custom` with `dateFrom` / `dateTo` (YYYY-MM-DD). Events are matched on their local date. Presets are computed from today's date in UTC. |
| `onlineOnly` | Online events only. On Eventbrite this searches the online-events listing, which isn't tied to a place, so locations are ignored there. Luma's discover feed lists mostly in-person events. |
| `freeOnly` | Free events only. |
| `maxEventsPerQuery` | Stop each query after this many matching events (default 100). |
| `includeDescription` | Fetch each event's full description, plus categories on Luma. This costs one extra request per event, so it is off by default. Eventbrite rows always include the short summary. |
| `maxItems` | Stop the whole run after this many saved events. |
| `maxConcurrency` | Queries run in parallel (default 3). |
| `maxAttempts` | Attempts per query, each on a fresh proxy session (default 3, maximum 5). |
| `requestTimeoutSecs` | Timeout for each HTTP request (default 30). |
| `proxyConfiguration` | Apify Proxy. The default is the RESIDENTIAL group pinned to the US, because Eventbrite answers datacenter IPs with HTTP 405. Luma also works from datacenter IPs, so Luma-only runs can switch to datacenter to save proxy cost. |

Example:

```json
{
  "sources": "both",
  "locations": ["Austin, TX", "London, United Kingdom"],
  "keywords": ["christmas market"],
  "dateRange": "custom",
  "dateFrom": "2026-12-01",
  "dateTo": "2026-12-24",
  "maxEventsPerQuery": 100
}
```

### Output

One row per event. Fields a platform doesn't publish are `null` or an empty list. Sample rows (the names are fictional):

```json
{
  "source": "eventbrite",
  "eventId": "1234567890123",
  "title": "Riverside Winter Makers Market",
  "url": "https://www.eventbrite.com/e/riverside-winter-makers-market-tickets-1234567890123",
  "startAt": "2026-12-05T10:00:00-06:00",
  "endAt": "2026-12-05T16:00:00-06:00",
  "timezone": "America/Chicago",
  "isOnline": false,
  "venueName": "Old Mill Hall",
  "address": "100 Example St, Austin, TX 78701",
  "city": "Austin",
  "region": "TX",
  "country": "US",
  "lat": 30.27,
  "lon": -97.74,
  "organizerName": "Riverside Makers Collective",
  "organizerUrl": "https://www.eventbrite.com/o/riverside-makers-collective-12345678901",
  "isFree": false,
  "priceMin": 5.0,
  "priceMax": 12.5,
  "currency": "USD",
  "isSoldOut": false,
  "salesStatus": "on_sale",
  "isCancelled": false,
  "ticketUrl": "https://www.eventbrite.com/checkout-external?eid=1234567890123",
  "imageUrl": "https://img.evbuc.com/...",
  "categories": ["Seasonal & Holiday", "Festival or Fair"],
  "tags": ["holidaymarket", "local"],
  "summary": "Forty local makers, warm drinks and live music.",
  "attendeeCount": null,
  "query": { "location": "Austin, TX", "keyword": "christmas market" },
  "scrapedAt": "2026-11-20T15:04:05.000000+00:00"
}
```

```json
{
  "source": "luma",
  "eventId": "evt-AbCdEfGhIjKlMnO",
  "title": "Founders Holiday Mixer",
  "url": "https://luma.com/abc123xy",
  "startAt": "2026-12-10T18:00:00+00:00",
  "endAt": "2026-12-10T21:00:00+00:00",
  "timezone": "Europe/London",
  "isOnline": false,
  "venueName": "The Example Rooms",
  "address": "1 Example Lane, London, UK",
  "city": "London",
  "region": "England",
  "country": "United Kingdom",
  "lat": 51.51,
  "lon": -0.12,
  "organizerName": "Northside Startup Club",
  "organizerUrl": "https://luma.com/northside-startups",
  "isFree": true,
  "priceMin": 0.0,
  "priceMax": 0.0,
  "currency": null,
  "isSoldOut": false,
  "salesStatus": "open",
  "isCancelled": false,
  "ticketUrl": "https://luma.com/abc123xy",
  "imageUrl": "https://images.lumacdn.com/...",
  "categories": [],
  "tags": [],
  "summary": null,
  "attendeeCount": 42,
  "query": { "location": "London, United Kingdom", "keyword": null },
  "scrapedAt": "2026-11-20T15:04:06.000000+00:00"
}
```

`startAt` and `endAt` are ISO 8601 in the event's own time zone. `attendeeCount` is Luma's public guest count; Eventbrite doesn't publish one. With `includeDescription` on, rows also carry `description` as plain text. The run's `FAILED_QUERIES` record looks like this:

```json
[{ "source": "luma", "query": "Smallville / all events", "error": "not found (\"Smallville\" is not one of Luma's discover cities; pass coordinates as \"latitude,longitude\" instead)", "attempts": 1 }]
```

### Limits

- **Eventbrite** search returns 20 events per page and stops at the last page it offers, usually a few hundred events per query. Eventbrite needs a place name. Coordinates are rejected with a clear message.
- **Luma** without a keyword walks the city's feed in date order. With a keyword, Luma's search returns at most 50 ranked results worldwide, and this Actor keeps those within 80 km of the location. Keyword queries on Luma are therefore small by design. Luma categories come only with `includeDescription`.
- Upcoming events only. Neither platform's public listing covers past events.
- Both sites can change their page layout or rate-limit a proxy IP. A challenge page or HTTP 429 is reported as such, retried on a fresh session after a short wait, and never solved or bypassed. If retries keep failing, check that the proxy is still set to the residential group.

### Pricing

Pay per saved event. Queries that fail are not charged, and the run stops at `maxItems` and `maxEventsPerQuery`, so the cost of a run is bounded before it starts. Full descriptions cost one extra request per event and make runs slower, so they are off by default.

### Legal note

This Actor collects public event listings that organizers publish so people can find and attend their events. It does not log in, and it does not collect attendee lists, guest names, or anyone's contact details. Organizer names and links are included only for organizations: businesses, venues and community groups. When a Luma event is hosted from an individual's personal calendar, `organizerName` and `organizerUrl` are left empty. You are responsible for using the data lawfully, including each platform's terms of use and the privacy law that applies to you.

# Actor input Schema

## `sources` (type: `string`):

Which event platforms to search. Both runs every location and keyword on Eventbrite and on Luma.

## `locations` (type: `array`):

One city per line, such as "Austin, TX", "London, United Kingdom" or "Berlin". Eventbrite also accepts its own place slug ("tx--austin"). Luma covers its discover cities (about 90 large cities worldwide); for anywhere else, enter "latitude,longitude".

## `keywords` (type: `array`):

Optional search terms, one per line (for example "christmas market", "new year's eve", "conference"). Each keyword runs as its own query in every location. Leave empty for all events.

## `dateRange` (type: `string`):

Keep events that take place in this window (by the event's local date; presets are computed from today's UTC date). Choose Custom to use the from/to dates below.

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

First date to include, YYYY-MM-DD. Used only when Dates is Custom.

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

Last date to include, YYYY-MM-DD. Used only when Dates is Custom.

## `onlineOnly` (type: `boolean`):

Keep only online events. On Eventbrite this searches the online-events listing, which is not tied to a place, so locations are ignored there. Luma's discover feed lists mostly in-person events, so expect few Luma results.

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

Keep only events marked free. Eventbrite filters server-side; Luma is filtered on its free flag.

## `maxEventsPerQuery` (type: `integer`):

A query is one source x location x keyword. Stop each query after this many matching events.

## `includeDescription` (type: `boolean`):

Fetch each event's full description (and Luma categories). Costs one extra request per event, so runs are slower. Off by default; Eventbrite rows always carry the short summary.

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

Stop the whole run after this many events are saved. Leave empty for no overall limit.

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

Queries fetched at the same time. Higher is faster but more likely to be rate-limited.

## `maxAttempts` (type: `integer`):

Each attempt uses a fresh proxy session and resumes where the last one stopped. Queries that still fail are listed in FAILED_QUERIES and are not charged.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for a single HTTP response before the attempt counts as failed and is retried on a fresh session.

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

Proxy settings. Eventbrite answers datacenter IPs with HTTP 405, so the default is the Apify RESIDENTIAL group pinned to the US. Luma works on datacenter too; switch to datacenter only for Luma-only runs.

## Actor input object example

```json
{
  "sources": "both",
  "locations": [
    "Austin, TX",
    "New York, NY"
  ],
  "keywords": [
    "christmas"
  ],
  "dateRange": "any",
  "onlineOnly": false,
  "freeOnly": false,
  "maxEventsPerQuery": 100,
  "includeDescription": false,
  "maxConcurrency": 3,
  "maxAttempts": 3,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

Dataset results in the "Events" view.

## `locations` (type: `string`):

Dataset results in the "Locations" view.

## `failed` (type: `string`):

JSON list of location and keyword queries that failed after every retry, with the error for each. Empty when everything succeeded. Failed inputs are never charged.

# 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 = {
    "locations": [
        "Austin, TX",
        "New York, NY"
    ],
    "keywords": [
        "christmas"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("signalclear/events-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 = {
    "locations": [
        "Austin, TX",
        "New York, NY",
    ],
    "keywords": ["christmas"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("signalclear/events-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 '{
  "locations": [
    "Austin, TX",
    "New York, NY"
  ],
  "keywords": [
    "christmas"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call signalclear/events-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,signalclear/events-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/7WXJHTa1LqoXhwArn/builds/33GNAO1czS1bExbDU/openapi.json
