# Universal Event Scraper (`techforce.global/universal-event-scraper`) Actor

The Universal Event Scraper is an Apify Actor that combines four specialized scrapers into a single run.
Allevents.in
Eventseye.com
District.in
Meetup.com.
It automatically handles dynamic content and produces clean structured data that can be exported or integrated into other systems.

- **URL**: https://apify.com/techforce.global/universal-event-scraper.md
- **Developed by:** [Techforce Global](https://apify.com/techforce.global) (community)
- **Categories:** Automation, Developer tools, Integrations
- **Stats:** 25 total users, 1 monthly users, 95.0% runs succeeded, 1 bookmarks
- **User rating**: 4.45 out of 5 stars

## Pricing

Pay per event + usage

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?

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

## Universal Event Scraper - 5 Event Platforms, One Unified Schema, One Run

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange?logo=apify)](https://apify.com/techforce.global/universal-event-scraper)
[![MCP Ready](https://img.shields.io/badge/MCP-Compatible-blue?logo=anthropic)](https://mcp.apify.com?tools=techforce.global/universal-event-scraper)
[![Sources](https://img.shields.io/badge/Sources-5%20platforms-blueviolet)](#the-five-sources)
[![Schema](https://img.shields.io/badge/Output-8%20identical%20fields-darkgreen)](#-output-contract--data-structure)
[![Pricing](https://img.shields.io/badge/Pricing-pay--per--event-green)](#-cost-control--pricing-transparency)

> Scrape **AllEvents.in, EventsEye.com, District.in, Meetup.com and Eventbrite** for one city in a **single run**, and get every result back in **exactly the same 8-field record** — no per-source shape to reconcile, no five separate Actors to orchestrate. Results can go straight into Notion, Slack, Airtable or Sheets through an MCP connector.

> ℹ️ **The unified schema is the whole point.** Each source has a completely different underlying structure — AllEvents renders HTML, EventsEye is a static trade-show directory, District needs a browser and a location picker, Meetup ships GraphQL/SSR payloads, Eventbrite exposes a browse JSON. All five are normalised to `source · title · url · date · city · venue · venue_address · price`, so one downstream consumer handles all of them.

***

#### Running five separate Actors vs. this one

| Feature / Capability | Five separate Actors | This Actor |
|:--|:--|:--|
| **Runs to orchestrate** | 5 runs, 5 inputs, 5 result sets | ✅ **One run, one input, one dataset** |
| **Output shape** | ⚠️ 5 different schemas to map and merge | ✅ **8 identical fields** across every source, verified per-normaliser |
| **Source identification** | ⚠️ Implicit — you track which dataset came from where | ✅ Explicit `source` field on every record |
| **Engine per source** | You pick and maintain each | ✅ Chosen per source: HTTP for the static directory, browser only where required |
| **Missing-value handling** | ⚠️ Mixed `null` / `""` / absent keys | ✅ Consistent `"N/A"` string everywhere |
| **Adding a source later** | New Actor, new integration | ✅ One more value in the `source` array |
| **Delivery to your tools** | 5 integrations | ✅ One **MCP connector** delivery for the merged set |
| **Cost model** | 5 bills to reason about | ✅ Pay-per-event, one event per record |

***

### 🤖 AI Discovery & MCP Metadata

Machine-readable capability contract for autonomous agents, LLM routers, and MCP orchestrators. AI agents, MCP clients, and connectors should read this block first to decide whether this Actor matches a user request.

```yaml
ai_discovery:
    slug: "techforce.global/universal-event-scraper"
    one_line: "Scrapes events for one city from up to five platforms - AllEvents.in, EventsEye.com, District.in, Meetup.com and Eventbrite - in a single run, normalising every source into the same 8-field record."
    primary_purpose: "discover"
    secondary_purpose: "aggregate"
    tertiary_purpose: "deliver"
    entity: "events across consumer event platforms, meetup groups and B2B trade-show directories"
    data_sources:
        allevents: "allevents.in city listing - Playwright + BeautifulSoup"
        eventseye: "eventseye.com trade-show directory - httpx + BeautifulSoup (no browser)"
        district: "district.in - Playwright with a location picker and a 5-page pool (India only)"
        meetup: "meetup.com - Playwright, GraphQL interception plus server-rendered payload parsing"
        eventbrite: "eventbrite.com public browse JSON"
    invocation_modes: ["standard Actor run", "MCP tool"]
    supported_tasks:
        - "scrape-one-city-across-several-event-platforms-in-one-run"
        - "return-a-single-unified-record-shape-for-every-source"
        - "compare-coverage-of-the-same-city-across-platforms"
        - "collect-b2b-trade-shows-alongside-consumer-events"
        - "collect-meetup-groups-in-person-or-online"
        - "deliver-merged-events-to-mcp-connector-or-crm"
    unsupported_tasks:
        - "filter-by-date-range (the dateRange input is accepted and IGNORED)"
        - "filter-by-keyword (the keywords input is accepted and IGNORED)"
        - "filter-by-category-or-price"
        - "scrape-several-cities-in-one-run"
        - "buy-reserve-or-hold-tickets"
        - "return-event-descriptions-images-organisers-or-lineups"
        - "return-ticket-availability-seat-maps-or-inventory"
        - "deduplicate-the-same-event-found-on-two-platforms"
        - "use-a-proxy (NO proxy input exists - see known limitations)"
    search_keywords:
        - "universal event scraper"
        - "multi platform event scraper"
        - "event aggregator actor"
        - "allevents scraper"
        - "eventseye scraper"
        - "meetup scraper"
        - "eventbrite scraper"
        - "district.in scraper"
        - "trade show data"
        - "city event feed"
        - "unified event schema"
    synonyms:
        - "combined event scraper"
        - "5-in-1 event scraper"
        - "cross-platform event extractor"
    related_concepts:
        [
            "event aggregation",
            "city event feeds",
            "trade show research",
            "community/meetup discovery",
            "event data normalisation",
        ]
    input_entities:
        - "source (array; REQUIRED; 1-5 of allevents | eventseye | district | meetup | eventbrite)"
        - "location (one city name, shared by every source)"
        - "limit (per source; 1-50, or 0 for no limit)"
        - "country_code (Meetup only)"
        - "eventType (Meetup only: inPerson | online)"
    output_entities:
        [
            "source",
            "title",
            "url",
            "date",
            "city",
            "venue",
            "venue_address",
            "price",
        ]
    input_rules:
        required_fields: ["source"]
        one_city_per_run: "location is a single city string shared by all selected sources"
        limit_semantics:
            per_source: true
            range: [1, 50]
            zero_means_unlimited: true
            allevents_exception: "the allevents source caps at 100 even when limit is 0"
            free_plan_cap: 2
        sources_run_sequentially: >
            Sources are executed one after another, deliberately: several launch their own
            Chromium and running them concurrently starves the lazy-loaded listing pages.
            Expect run time to be the SUM of the selected sources.
        ignored_inputs:
            - "dateRange - accepted, never read"
            - "keywords - accepted, never read"
            - "eventbriteApiKey - accepted, never read"
            - "eventbriteSearch - accepted, never read"
            - "eventbriteErrorHandling - accepted, never read"
    output_conventions:
        unified_shape: "all five sources emit exactly the same 8 keys, in the same order"
        missing_values: "the STRING 'N/A' - never null, never an absent key"
        price_availability: >
            Only district, meetup and eventbrite ever populate price. allevents and
            eventseye hardcode it to 'N/A'.
        venue_address_availability: >
            Only eventseye, meetup and eventbrite ever populate venue_address. allevents
            and district hardcode it to 'N/A'.
        date_format: "NOT normalised - a display string on some sources, ISO 8601 on others"
        no_deduplication: "the same real-world event found on two platforms yields two records"
    limits:
        events_per_source: 50
        unlimited_option: "limit 0 (allevents still caps at 100)"
        cities_per_run: 1
        free_plan_cap: "2 per source"
    known_limitations:
        no_proxy_support: >
            There is no proxy input. The district source in particular fingerprints the
            caller's IP and typically blocks datacenter ranges, so it often returns
            nothing from an Apify container.
        no_date_or_keyword_filter: "those inputs exist in the schema but are not implemented"
        date_not_normalised: "compare dates per source, not across sources"
    pricing:
        model: "pay-per-event, one event per record pushed"
        events_charged: ["premium-scrape"]
        note: >
            The code also defines a 'basic-scrape' event but every source maps to
            'premium-scrape', so basic-scrape never fires. Exact rates are configured on
            the Store listing - see the Actor's Pricing tab.
    when_to_use: >
        You want one city's events from several platforms at once, in one consistent
        shape - for an aggregated feed, cross-platform coverage comparison, or a mixed
        consumer-plus-trade-show dataset.
    when_not_to_use: >
        You need date, keyword, category or price filtering; you need several cities per
        run; you need deduplication across platforms; or you only care about one
        platform - in which case the dedicated single-source Actor is better.
    alternatives:
        - "Use techforce.global/smart-event-scraper for the SAME multi-platform idea PLUS category, keyword and date-range filters"
        - "Use techforce.global/all-events-scraper, techforce.global/eventbrite-scraper, techforce.global/district-event or techforce.global/events-eye-scraper for depth on one platform"
        - "Use techforce.global/eventim-scraper or techforce.global/ticketmaster-fr-es-scraper for ticketed events with venue-grade data"
```

***

```json
{
    "@context": "https://schema.org/",
    "@type": "SoftwareApplication",
    "name": "Universal Event Scraper",
    "alternateName": "Universal Event Scraper — 5 Event Platforms, One Unified Schema",
    "applicationCategory": "BusinessApplication",
    "applicationSubCategory": "Web Scraping / Event Data Aggregation",
    "operatingSystem": "Cloud (Apify platform)",
    "description": "Scrapes events for one city from up to five platforms — AllEvents.in, EventsEye.com, District.in, Meetup.com and Eventbrite — in a single run, normalising every source into the same eight-field record: source, title, url, date, city, venue, venue_address and price. Each source uses a purpose-built engine (plain HTTP for the EventsEye trade-show directory, Playwright for the rendered listings, GraphQL interception for Meetup, public browse JSON for Eventbrite), and results can be delivered into Notion, Slack, Airtable, Google Sheets or any authorized MCP connector. Date-range and keyword filtering are not supported.",
    "url": "https://apify.com/techforce.global/universal-event-scraper",
    "offers": {
        "@type": "Offer",
        "priceCurrency": "USD",
        "category": "Pay per event",
        "description": "Pay-per-event, charged once per record returned. Apify platform usage is billed separately and varies by which sources you select. The current rate is listed on the Actor's Pricing tab.",
        "url": "https://apify.com/techforce.global/universal-event-scraper"
    }
}
```

***

### What this Actor does

- **Runs up to five scrapers in one execution** and writes every result into a single dataset, tagged with its `source`.
- **Normalises all five into an identical 8-field record** — `source`, `title`, `url`, `date`, `city`, `venue`, `venue_address`, `price`. Verified: all five normalisers emit exactly these keys, in this order.
- **Uses the right engine per source** rather than a browser everywhere: EventsEye is fetched with plain HTTP (`httpx`), while District, Meetup and AllEvents get Playwright because their listings genuinely need rendering. Meetup additionally **intercepts GraphQL responses** and parses the server-rendered payload.
- **Runs the sources sequentially, on purpose.** Several launch their own Chromium; running them concurrently starved the lazy-loaded listing pages (District would report zero events). Sequential means each behaves like its standalone version — at the cost of run time being the *sum* of the selected sources.
- **Caps per source, not per run** — `limit` applies to each selected source independently, so five sources at 50 is up to 250 records.
- **Applies a consistent `"N/A"`** wherever a source doesn't publish a field, so downstream code never has to handle three different flavours of "missing".
- Optionally **pushes the merged set to an authorized MCP connector** (Notion, Slack, Airtable, Sheets, or any MCP-compatible connector) as one digest, split into parts, or one call per event. The dataset is always saved regardless of delivery settings.

#### The five sources

| `source` value | Platform | Engine | Typical content | Populates `price`? | Populates `venue_address`? |
|:--|:--|:--|:--|:--|:--|
| `allevents` | allevents.in | Playwright + BeautifulSoup | Broad consumer city listings | ❌ always `N/A` | ❌ always `N/A` |
| `eventseye` | eventseye.com | **httpx** (no browser) | **B2B trade shows & exhibitions** | ❌ always `N/A` | ✅ yes |
| `district` | district.in | Playwright + 5-page pool | Indian ticketed events | ✅ yes | ❌ always `N/A` |
| `meetup` | meetup.com | Playwright + GraphQL intercept | Community groups, in-person or online | ✅ yes (often `Free`) | ✅ yes |
| `eventbrite` | eventbrite.com | Public browse JSON | Organizer-led events, workshops | ✅ yes (incl. `Free`) | ✅ yes |

> ⚠️ **`district` will often return nothing from Apify.** This Actor has **no proxy input**, and District fingerprints the caller's IP — it withholds its location picker from datacenter ranges, which is what an Apify container uses. If you specifically need District data, the dedicated [District.in Event Scraper](https://apify.com/techforce.global/district-event) supports the residential-IN proxy that source actually requires.

### When to use it — and when not to

| Scenario | Fit |
|---|---|
| One city's events from several platforms, in one consistent shape | ✅ Strong (the core use case) |
| Comparing how well platforms cover the same city | ✅ Strong — group by `source` |
| A mixed dataset of consumer events **and** B2B trade shows | ✅ Strong — `allevents` + `eventseye` |
| Community/meetup discovery alongside ticketed events | ✅ Strong — `meetup` + `eventbrite` |
| Feeding a merged feed into Notion / Airtable / Slack | ✅ Strong (`deliveryMode`) |
| Trade shows only, no browser cost | ✅ Strong — `["eventseye"]` alone is HTTP-only |
| **Filtering by date range or keyword** | ❌ Those inputs exist but are **not implemented** — ➡️ [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) |
| Filtering by category or price | ❌ No such input — ➡️ [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) |
| Several cities in one run | ❌ One `location` per run — loop from your side |
| Deduplicating an event that appears on two platforms | ❌ Not done — you get two records |
| Only one platform matters to you | ⚠️ A dedicated single-source Actor gives more fields and options |
| District.in data specifically | ⚠️ No proxy support here — use [District.in Event Scraper](https://apify.com/techforce.global/district-event) |
| Comparing `date` values across sources | ⚠️ `date` is **not** normalised — formats differ per source |
| Event descriptions, images, organisers, lineups | ❌ Eight fields only |
| Ticket availability, seat maps, inventory | ❌ Not returned |
| Buying or reserving tickets | ❌ Read-only |

***

### ⚡ Execution Rules (Avoid Common Agent Failures)

1. **`source` is required and is an array.** Pass one to five of `allevents`, `eventseye`, `district`, `meetup`, `eventbrite`. An unrecognised value is skipped with a warning, not an error — so a typo yields a quietly empty run.
2. **`limit` is per source, not per run.** Five sources at 50 is up to **250 records**, and 250 billable events.
3. **`limit: 0` means no limit** — it bypasses the schema's maximum of 50 entirely. Use a spend cap with it. Note the exception: the `allevents` source caps at **100** even when `limit` is 0.
4. **Free Apify plans are capped at 2 records per source** — so a 5-source run returns up to 10 items. That's a successful run.
5. **`dateRange` and `keywords` are accepted and silently ignored.** No source filters by either. If you need that filtering, use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper); otherwise filter the dataset afterwards.
6. **Three more inputs are inert:** `eventbriteApiKey`, `eventbriteSearch` and `eventbriteErrorHandling`. Selecting `eventbrite` in `source` is what enables that scraper.
7. **Sources run sequentially, so run time is the sum of them.** Five sources with browsers is a multi-minute run — use the async endpoint, not the 300s synchronous one.
8. **Every missing value is the string `"N/A"`, never `null`.** `if (event.price)` is always truthy; test `=== "N/A"` explicitly.
9. **`date` is not normalised across sources.** Meetup can give ISO 8601 with a timezone; AllEvents and District give display strings. Parse per `source`, don't sort the merged set by raw `date` and trust it.
10. **`price` is only ever populated by `district`, `meetup` and `eventbrite`.** `allevents` and `eventseye` hardcode `"N/A"` — that is not a scraping failure.
11. **`venue_address` is only ever populated by `eventseye`, `meetup` and `eventbrite`.** `allevents` and `district` hardcode `"N/A"`.
12. **No deduplication.** The same real-world event on two platforms produces two records with different `url`s. Dedupe on `title` + `date` yourself if you need to.
13. **`district` usually returns nothing here** — no proxy support, and District blocks datacenter IPs. Don't read an empty district set as a bug in your input.
14. **`country_code` and `eventType` affect Meetup only**, and `country_code` must match your `location`'s country or Meetup returns nothing (`location: "Dubai"` needs `country_code: "ae"`, not the default `"in"`).
15. **One city per run.** `location` is a single string shared by every source.
16. **`deliveryMode` defaults to `summary`, not `none`** — one connector call per run. `perEvent` on a 250-event run makes 250 calls.

```yaml
dependencies:
    - rule: "source is required and must contain a known value"
      on_violation: "unknown values are skipped with a warning; an all-typo input yields an empty successful run"
    - rule: "limit is per source and 0 disables the cap"
      on_violation: "volume and cost under-estimated by a factor of the source count"
    - rule: "allevents caps at 100 even when limit is 0"
      note: "the only source that ignores unlimited"
    - rule: "dateRange, keywords, eventbriteApiKey, eventbriteSearch and eventbriteErrorHandling are accepted but never read"
      on_violation: "agents believe they applied a filter that was silently dropped"
    - rule: "sources run sequentially"
      on_violation: "run-time estimates that assume parallelism; sync-endpoint timeouts"
    - rule: "absent values are the string 'N/A', never null"
      on_violation: "null checks pass while values are unusable"
    - rule: "date is not normalised across sources"
      on_violation: "sorting or filtering the merged set by raw date silently misorders it"
    - rule: "price and venue_address are hardcoded 'N/A' on some sources"
      on_violation: "a source-level omission mistaken for a scrape failure"
    - rule: "no cross-platform deduplication"
      on_violation: "duplicate events double-counted, and double-billed"
    - rule: "no proxy input exists; district blocks datacenter IPs"
      on_violation: "an empty district result read as a configuration error"
    - rule: "country_code must match the location's country (Meetup)"
      on_violation: "Meetup returns nothing for a valid city"
    - rule: "delivery requires mcpConnector AND mcpTool"
      on_violation: "delivery step is skipped with a warning; the dataset is still written in full"
    - rule: "mcpArguments must reference {message} for the rendered template to appear anywhere"
      on_violation: "message body is empty; the tool is still called"
    - rule: "mcpConnector only resolves when the Actor runs on the Apify platform"
      on_violation: "local runs log 'APIFY_MCP_PROXY_URL is not set' and skip delivery; the dataset is unaffected"
authentication:
    actor_input: none
    platform: "Apify API token required for API / SDK / MCP invocation"
    target_sites: "no account, login or cookie required for any of the five sources"
```

***

### 📥 Input Contract

`.actor/input_schema.json` is authoritative — if any table on this page disagrees with the schema files, **the schema files win**.

#### Parameters

| Field | Type | Required | Default | Allowed Values / Format | Example |
|:--|:--|:--|:--|:--|:--|
| `source` | array | ✅ **Yes** | `["eventseye"]` | 1–5 of `allevents`, `eventseye`, `district`, `meetup`, `eventbrite` | `["allevents","meetup"]` |
| `location` | string | ⬜ No | `"Dubai"` | One city name, shared by all sources | `"mumbai"` |
| `limit` | integer | ⬜ No | `50` | `1`–`50` **per source**, or `0` for no limit | `25` |
| `country_code` | string | ⬜ No | `"in"` | ISO 3166-1 alpha-2 — **Meetup only** | `"ae"` |
| `eventType` | enum | ⬜ No | `"inPerson"` | `inPerson`, `online` — **Meetup only** | `"online"` |

#### ⚠️ Inputs that exist but do nothing

These are present in the schema and accepted by the API, but **no code reads them**. They are documented here so an agent doesn't believe a filter was applied.

| Field | What you might expect | What actually happens |
|:--|:--|:--|
| `dateRange` | Date-range filtering | **Ignored.** Filter the dataset's `date` field afterwards, or use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) |
| `keywords` | Keyword filtering | **Ignored.** Filter the dataset's `title` field afterwards |
| `eventbriteApiKey` | Authenticated Eventbrite access | **Ignored.** The Eventbrite source reads the public browse JSON and does not authenticate |
| `eventbriteSearch` | Enable/disable the Eventbrite source | **Ignored.** Selecting `eventbrite` in `source` is what enables it |
| `eventbriteErrorHandling` | Toggle error handling | **Ignored.** Eventbrite errors are always caught and logged |

#### Examples

**Broadest sweep — everything for one city**

```json
{
    "source": ["allevents", "eventseye", "meetup", "eventbrite"],
    "location": "mumbai",
    "limit": 50,
    "country_code": "in",
    "deliveryMode": "none"
}
```

**Trade shows only — HTTP-only, cheapest and fastest**

```json
{
    "source": ["eventseye"],
    "location": "berlin",
    "limit": 50,
    "deliveryMode": "none"
}
```

**Meetup in a non-Indian city — `country_code` must match**

```json
{
    "source": ["meetup"],
    "location": "Dubai",
    "country_code": "ae",
    "eventType": "inPerson",
    "limit": 50,
    "deliveryMode": "none"
}
```

**Online communities only**

```json
{
    "source": ["meetup"],
    "location": "london",
    "country_code": "gb",
    "eventType": "online",
    "limit": 50,
    "deliveryMode": "none"
}
```

**Cheap validation run before committing**

```json
{
    "source": ["allevents", "eventbrite"],
    "location": "ahmedabad",
    "limit": 2,
    "deliveryMode": "none"
}
```

**Consumer + B2B side by side**

```json
{
    "source": ["allevents", "eventseye"],
    "location": "dubai",
    "limit": 50,
    "deliveryMode": "none"
}
```

#### MCP Delivery Configuration (Optional)

Delivery pushes **event data into your own tools**. It never buys, reserves or contacts anything.

| Parameter | Type | Default | Description |
|:--|:--|:--|:--|
| `mcpConnector` | string | `""` | Authorized MCP connector on your Apify account (Notion, Slack, Airtable, Sheets, …). Leave empty for dataset-only runs. |
| `deliveryMode` | enum | **`summary`** | `summary` (one call with every event) · `chunked` (split across calls) · `perEvent` (one call per event) · `none`. |
| `mcpTool` | string | `""` | Tool name on the connector: `create_page`, `send_message`, `append_row`, `create_record`. Run once with a connector selected — the log lists the available tools. |
| `mcpArguments` | object | `{}` | Arguments passed to the tool. String leaves support `{placeholders}`. |
| `mcpMessageTemplate` | string | `""` | Template rendered and exposed to `mcpArguments` as `{message}`. |

**Summary & chunked placeholders:** `{location}`, `{sources}` (comma-separated list of the sources that actually returned data), `{eventCount}`, `{events}`, `{message}` — plus `{part}` and `{partCount}` in chunked mode.

**Per-event placeholders:** every output field — `{source}`, `{title}`, `{url}`, `{date}`, `{city}`, `{venue}`, `{venue_address}`, `{price}` — plus `{message}`.

A Slack digest of the merged run:

```json
{
    "source": ["allevents", "meetup", "eventbrite"],
    "location": "mumbai",
    "limit": 50,
    "mcpConnector": "<your-authorized-slack-connector>",
    "deliveryMode": "summary",
    "mcpTool": "send_message",
    "mcpArguments": { "channel": "#events", "text": "{message}" },
    "mcpMessageTemplate": "📅 {eventCount} events in {location} from {sources}:\n\n{events}"
}
```

A large merged set split across Notion sub-pages:

```json
{
    "source": ["allevents", "eventseye", "meetup", "eventbrite"],
    "location": "berlin",
    "limit": 50,
    "mcpConnector": "<your-authorized-notion-connector>",
    "deliveryMode": "chunked",
    "mcpTool": "notion-create-pages",
    "mcpArguments": {
        "parent": { "page_id": "<your-page-id>" },
        "pages": [
            {
                "properties": {
                    "title": "Events in {location} — part {part}/{partCount}"
                },
                "content": "{events}"
            }
        ]
    }
}
```

One Airtable row per event, keeping the source tag:

```json
{
    "source": ["eventseye"],
    "location": "dubai",
    "limit": 50,
    "mcpConnector": "<your-authorized-airtable-connector>",
    "deliveryMode": "perEvent",
    "mcpTool": "create_record",
    "mcpArguments": {
        "fields": {
            "Platform": "{source}",
            "Event": "{title}",
            "Date": "{date}",
            "City": "{city}",
            "Venue": "{venue}",
            "Address": "{venue_address}",
            "Price": "{price}",
            "Link": "{url}"
        }
    }
}
```

***

### 📤 Output Contract & Data Structure

**Storage:** Apify Dataset — one JSON object per event, pushed as each source scrapes.
**Pagination:** `limit` & `offset` on the dataset items endpoint.
**Duplicates:** **not deduplicated across sources.** The same event on two platforms is two records. Dedupe on `url`, or on `title` + `date` for cross-platform matching.

#### The unified record — 8 fields, identical for every source

| Field | Type | Empty value | Meaning | Populated by |
|---|---|---|---|---|
| `source` | string | never empty | Which platform produced this record: `allevents`, `eventseye`, `district`, `meetup`, `eventbrite` | all |
| `title` | string | `"N/A"` | Event or exhibition name | all |
| `url` | string | `"N/A"` | Link to the event on its platform — **the dedupe key** | all |
| `date` | string | `"N/A"` | Start date/time — **format differs per source**, not normalised | all |
| `city` | string | `"N/A"` | City (echoed from your `location` on most sources) | all |
| `venue` | string | `"N/A"` | Venue or location name | all |
| `venue_address` | string | `"N/A"` | Street address | ⚠️ `eventseye`, `meetup`, `eventbrite` only |
| `price` | string | `"N/A"` | Price as a display string, or `Free` | ⚠️ `district`, `meetup`, `eventbrite` only |

> ⚠️ **`"N/A"` is a string, not `null`, and it has two different meanings.** On `price` and `venue_address` it may mean *this source never provides that field* (see the column above) rather than *this event didn't publish one*. Check `source` before concluding anything about a gap.

There is no description, image, category, organiser, lineup, availability or ID field. Eight fields is the whole contract.

#### Example — `eventseye` (trade show, has an address, never a price)

```json
{
    "source": "eventseye",
    "title": "GULFOOD",
    "url": "https://www.eventseye.com/fairs/f-gulfood-1234.html",
    "date": "Feb 2027",
    "city": "Dubai",
    "venue": "Dubai World Trade Centre",
    "venue_address": "Sheikh Zayed Road, Dubai, United Arab Emirates",
    "price": "N/A"
}
```

#### Example — `meetup` (ISO date, price often `Free`)

```json
{
    "source": "meetup",
    "title": "Mumbai Python User Group — Monthly Meetup",
    "url": "https://www.meetup.com/mumbai-python/events/301234567/",
    "date": "2026-09-18T18:30:00+00:00",
    "city": "mumbai",
    "venue": "WeWork Enam Sambhav",
    "venue_address": "C-20, G Block, Bandra Kurla Complex, Mumbai, MH",
    "price": "Free"
}
```

#### Example — `allevents` (display date, no price, no address by design)

```json
{
    "source": "allevents",
    "title": "Sunburn Arena ft. Martin Garrix",
    "url": "https://allevents.in/mumbai/sunburn-arena/1234567890",
    "date": "Sat, 12 Sep, 7:00 pm",
    "city": "Mumbai",
    "venue": "NSCI Dome, Worli",
    "venue_address": "N/A",
    "price": "N/A"
}
```

#### Working with a merged set

```python
from collections import Counter

## Always branch on `source` - "N/A" can mean "this source never provides it".
print(Counter(e["source"] for e in items))          # coverage per platform

priced = [e for e in items if e["price"] != "N/A"]   # district / meetup / eventbrite only
addressed = [e for e in items if e["venue_address"] != "N/A"]

## `date` is NOT normalised - only parse within a source you know the format of.
meetup_dated = [e for e in items if e["source"] == "meetup" and e["date"] != "N/A"]

## No cross-platform dedup is done for you.
seen, unique = set(), []
for e in items:
    key = (e["title"].strip().lower(), e["date"])
    if key not in seen:
        seen.add(key)
        unique.append(e)
print(f"{len(items)} records → {len(unique)} after cross-platform dedupe")
```

#### What a successful run looks like

```json
{
    "status": "SUCCEEDED",
    "defaultDatasetId": "<datasetId>",
    "stats": { "itemCount": 137 }
}
```

`itemCount` below `limit × len(source)` is normal — some sources return less, and `district` frequently returns nothing at all without a proxy. `itemCount: 0` is a **valid empty result**. Read the per-source log lines to tell the causes apart:

| Log line | Meaning |
|:--|:--|
| `Running scraper: '<source>'` | That source started — one line per selected source |
| `Unknown source: '<value>' — skipping.` | A typo or unsupported value; nothing ran for it |
| `[allevents] Input 'location' is missing!` | `location` was empty |
| `[allevents] Failed to load page` | The listing page didn't load |
| `[<source>] ✔ Scraped N events` | That source's own count |
| `Free plan detected — capping results to 2 per source.` | Free-plan cap applied |
| `Charge failed for '<event>'` | Billing event failed; the record was still pushed |

***

### ▶️ Invocation & Integration

**Lifecycle:** Discover → Understand → Configure → Execute → Retrieve → Validate. Authenticate every call with `Authorization: Bearer <APIFY_TOKEN>`.

#### Apify Console

Open the Actor → **Input** tab → select one or more **Sources** → type a **Location** → set **Max Events (per source)** → *(for Meetup)* set **Country code** to match the city → *(optional)* pick an **MCP connector**, tool and template → **Start** → export from the **Dataset** tab (JSON, CSV, Excel, XML).

#### Model Context Protocol (MCP)

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com/?tools=techforce.global/universal-event-scraper"
```

#### Python (apify-client)

```python
import os
from collections import Counter
from apify_client import ApifyClient

client = ApifyClient(os.getenv("APIFY_TOKEN"))
ACTOR = "techforce.global/universal-event-scraper"

run = client.actor(ACTOR).call(run_input={
    "source": ["allevents", "eventseye", "meetup", "eventbrite"],
    "location": "mumbai",
    "limit": 50,               # PER SOURCE → up to 200 records here
    "country_code": "in",      # Meetup only; must match the city's country
    "deliveryMode": "none",
})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(f"{len(items)} records")
print(Counter(e["source"] for e in items))     # which platforms actually delivered

## "N/A" is a STRING sentinel, and on price/venue_address it may simply mean
## "this source never provides that field".
for event in items[:10]:
    price = event["price"] if event["price"] != "N/A" else "—"
    print(f'[{event["source"]:<10}] {event["title"][:45]}')
    print(f'{"":<13} {event["date"]}  ·  {event["venue"]}  ·  {price}')
```

**Multi-city sweep** — one run per city, since `location` is a single string:

```python
CITIES = ["mumbai", "delhi", "bengaluru"]
all_events = []
for city in CITIES:
    run = client.actor(ACTOR).call(run_input={
        "source": ["allevents", "eventbrite"],
        "location": city,
        "limit": 50,
        "deliveryMode": "none",
    })
    all_events += client.dataset(run["defaultDatasetId"]).list_items().items
print(f"{len(all_events)} events across {len(CITIES)} cities")
```

#### JavaScript / TypeScript (apify-client)

```typescript
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const ACTOR = "techforce.global/universal-event-scraper";

const run = await client.actor(ACTOR).call({
    source: ["eventseye", "eventbrite"],
    location: "berlin",
    limit: 50,
    deliveryMode: "none",
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();

// Group by platform - the `source` tag is what makes a merged set usable.
const bySource = items.reduce<Record<string, number>>((acc, e) => {
    acc[e.source] = (acc[e.source] ?? 0) + 1;
    return acc;
}, {});
console.log(bySource);

// price is only ever real on district / meetup / eventbrite
const priced = items.filter((e) => e.price !== "N/A");
console.log(`${priced.length} of ${items.length} records carry a price`);
```

#### cURL — asynchronous with a spend cap (recommended)

Sources run **sequentially**, so a multi-source run takes minutes. Prefer async over the 300s synchronous endpoint.

```bash
## 1. Start execution with a hard spend limit
curl -X POST -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{"source":["allevents","eventseye","meetup","eventbrite"],"location":"mumbai","limit":50,"deliveryMode":"none"}' \
  'https://api.apify.com/v2/acts/techforce.global~universal-event-scraper/runs?maxTotalChargeUsd=2.00'

## 2. Poll
curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  'https://api.apify.com/v2/actor-runs/%3CrunId%3E'

## 3. Retrieve when SUCCEEDED
curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  'https://api.apify.com/v2/datasets/%3CDATASET_ID%3E/items?clean=true&format=json'
```

#### cURL — synchronous (single source, small limit only)

```bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{"source":["eventseye"],"location":"dubai","limit":20,"deliveryMode":"none"}' \
  'https://api.apify.com/v2/acts/techforce.global~universal-event-scraper/run-sync-get-dataset-items'
```

***

### ⚠️ Autonomous Agent Error Handling & Resolution Matrix

| Error Code | Detection Condition | Underlying Cause | Deterministic Agent Action |
|:--|:--|:--|:--|
| `AUTH_INVALID` | HTTP `401` from Apify | Missing or malformed `APIFY_TOKEN` | **Abort & Prompt User:** request a valid Apify API token. |
| `RATE_LIMITED` | HTTP `429` from Apify | Apify API rate / concurrency limits | **Retry:** exponential backoff (2s, 4s, 8s). |
| `SYNC_TIMEOUT` | HTTP `408` on the sync endpoint | Sources run sequentially; multi-source runs exceed 300s | **Reconfigure:** async `POST /runs` + polling. |
| `MISSING_SOURCE` | Input validation rejection | `source` is required | **Modify Input:** supply an array with at least one known value. |
| `UNKNOWN_SOURCE` | `SUCCEEDED`, 0 items, log `Unknown source: '<x>' — skipping.` | Typo or unsupported platform name | **Modify Input:** use `allevents`, `eventseye`, `district`, `meetup`, `eventbrite`. |
| `MISSING_LOCATION` | Log `[allevents] Input 'location' is missing!` | `location` empty | **Modify Input:** supply a city. |
| `VOLUME_UNDERESTIMATED` | Far more records than expected | `limit` is **per source** | **Fix Expectation:** total ≈ limit × source count. |
| `UNLIMITED_RUNAWAY` | Very large run, high spend | `limit: 0` removes the cap entirely | **Reconfigure:** set a real limit and `maxTotalChargeUsd`. |
| `ALLEVENTS_CAPPED_100` | `allevents` stops at 100 with `limit: 0` | That source caps at 100 regardless | **Fix Expectation:** documented behaviour. |
| `FREE_PLAN_CAP` | 2 records per source, upgrade banner in the log | Free Apify plan cap | **Prompt User:** upgrade for full-volume runs. |
| `DATE_FILTER_IGNORED` | `dateRange` set, results outside it | **Not implemented** — the input is read by nothing | **Re-route:** [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper), or filter the dataset. |
| `KEYWORD_FILTER_IGNORED` | `keywords` set, unrelated results | **Not implemented** | **Re-route** or filter the dataset. |
| `EVENTBRITE_TOGGLE_IGNORED` | `eventbriteSearch: false` yet Eventbrite ran | The toggle is inert; `source` controls it | **Modify Input:** remove `eventbrite` from `source`. |
| `DISTRICT_EMPTY` | `district` selected, zero district records | **No proxy support**; District blocks datacenter IPs | **Re-route:** [District.in Event Scraper](https://apify.com/techforce.global/district-event) with a residential-IN proxy. |
| `MEETUP_EMPTY` | `meetup` selected, zero meetup records | `country_code` doesn't match the `location`'s country | **Modify Input:** set `country_code` correctly (e.g. `ae` for Dubai). |
| `SOURCE_EMPTY` | One source returns nothing, others work | That platform has no coverage for the city, or its layout changed | **Accept**, or drop that source. Check its `[<source>]` log lines. |
| `NA_NOT_NULL` | Null checks pass but values unusable | Missing values are the string `"N/A"` | **Fix Code:** compare against `"N/A"`. |
| `FIELD_NEVER_POPULATED` | `price` or `venue_address` always `"N/A"` | That **source** never provides it — not a failure | **Fix Expectation:** check the source-capability table. |
| `DATE_FORMAT_MIXED` | Date parsing fails on some records | `date` is **not** normalised across sources | **Fix Code:** branch on `source` before parsing. |
| `DUPLICATE_ACROSS_SOURCES` | The same event twice | No cross-platform dedup is performed | **Fix Code:** dedupe on `title` + `date`. |
| `MULTI_CITY_UNSUPPORTED` | Agent passes a city array | `location` is a single string | **Loop:** one run per city. |
| `NO_EVENT_DETAIL` | No description, image, organiser or lineup | Eight fields only | **Fix Expectation:** open `url`. |
| `CHARGE_FAILED` | Log `Charge failed for '<event>'` | The billing event call failed | **Not data loss** — the record was still pushed. Report to support if persistent. |
| `DELIVERY_SKIPPED` | Log `no tool name was provided` | `mcpConnector` set but `mcpTool` empty | **Modify Input:** set `mcpTool`; the dataset is already saved. |
| `DELIVERY_TOOL_UNKNOWN` | Log `Tool 'x' is not available on this connector` | Wrong tool name for that connector | **Modify Input:** pick a name from the `Available tools:` list in the same log line. |
| `DELIVERY_EMPTY` | Connector called, body empty | `mcpArguments` omitted the `{message}` placeholder | **Modify Input:** map `{message}` inside `mcpArguments`. |
| `DELIVERY_FLOOD` | Hundreds of connector calls | `deliveryMode: "perEvent"` | **Reconfigure:** use `summary` or `chunked`. |
| `MCP_PROXY_MISSING` | Log `APIFY_MCP_PROXY_URL is not set` | Running locally instead of on the platform | **Reconfigure:** `apify push` and run on the platform. |
| `CHARGE_LIMIT` | Run aborted before all sources finished | `maxTotalChargeUsd` spend threshold reached | **Prompt User:** confirm a higher spend cap. |

> **Reading run status:** a source that returns nothing does not fail the run — each source is caught independently and the others still deliver, so `SUCCEEDED` with fewer sources than you selected is normal. The log names which source came back empty and why. Only a `FAILED` status is a platform-level failure. Every source-side row, quoted log line and field condition in the table above comes from this Actor’s own source; the `401`/`408`/`429` rows are standard Apify API responses.

***

### 🗣️ Natural Language → Actor Mapping

| User says | Intent | Constructed Actor input |
|---|---|---|
| "Get me all the events in Mumbai from every platform" | Multi-source sweep | `{"source":["allevents","eventseye","meetup","eventbrite"],"location":"mumbai","limit":50}` |
| "What trade shows are happening in Dubai?" | B2B only | `{"source":["eventseye"],"location":"dubai","limit":50}` |
| "Find tech meetups in Bengaluru" | Community | `{"source":["meetup"],"location":"bengaluru","country_code":"in"}` then filter `title` |
| "Online meetups I can join from anywhere" | Online only | `{"source":["meetup"],"location":"london","country_code":"gb","eventType":"online"}` |
| "Workshops and classes in Berlin" | Organizer-led | `{"source":["eventbrite"],"location":"berlin","limit":50}` |
| "Compare how many events each platform lists for Delhi" | Coverage analysis | run all sources, then group by `source` |
| "Consumer events and business expos side by side" | Mixed dataset | `{"source":["allevents","eventseye"],"location":"<city>"}` |
| "Which of these events are free?" | Price filter | run, then filter `price == "Free"` (only 3 sources populate price) |
| "Build an event feed for three cities" | Multi-city | Loop one run per city — `location` is a single string |
| "Push the merged feed into Notion" | Delivery | `{"mcpConnector":"notion","deliveryMode":"chunked","mcpTool":"notion-create-pages"}` |
| "Post a daily summary to Slack" | Digest delivery | schedule + `{"deliveryMode":"summary","mcpTool":"send_message"}` |
| **"Only events next weekend"** | Date filter | ⚠️ `dateRange` is **ignored** — ➡️ [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper), or filter `date` afterwards |
| **"Only events matching 'AI'"** | Keyword filter | ⚠️ `keywords` is **ignored** — filter `title` afterwards |
| "Why did District return nothing?" | Diagnosis | ⚠️ No proxy support here — ➡️ [District.in Event Scraper](https://apify.com/techforce.global/district-event) |
| "Remove the duplicates across platforms" | Dedup | ⚠️ Not done for you — dedupe on `title` + `date` |
| "Get me the event description and image" | Detail | ❌ Eight fields only — open `url` |
| "How many tickets are left?" | Inventory | ❌ Not returned |
| "Book two tickets" | Transaction | ❌ Read-only |

**Should NOT route here:** date-range, keyword, category or price filtering at the source · several cities in one run · cross-platform deduplication · District.in data specifically (no proxy support) · event descriptions, images, organisers or lineups · ticket availability, seat maps or inventory · buying or reserving tickets · depth on a single platform where the dedicated Actor offers more fields.

***

### 🧭 Agent Execution & Routing Logic

```text
[Input User Query]
       │
       ▼
1. Does the user need FILTERING at the source (date, keyword, category, price)?
       │ YES → Route to Smart Event Scraper. This Actor's dateRange/keywords inputs
       │        are ACCEPTED AND IGNORED — promising them here is wrong
       │ NO
       ▼
2. Do they care about only ONE platform?
       │ YES → prefer the dedicated Actor (more fields, more options):
       │        allevents.in → All Events Scraper
       │        eventbrite   → Eventbrite Scraper
       │        district.in  → District.in Event Scraper (has the proxy it needs)
       │        eventseye    → Events Eye Scraper
       │ NO (they want several platforms in one shape) → continue
       ▼
3. Pick the sources. Note per-source capability:
       ├── price          → district | meetup | eventbrite ONLY
       ├── venue_address  → eventseye | meetup | eventbrite ONLY
       ├── trade shows    → eventseye (and it is HTTP-only, so cheapest)
       └── district       → will likely return NOTHING here (no proxy support)
       ▼
4. One city per run. Several cities → loop.
       ▼
5. Set limit PER SOURCE (1-50; 0 = unlimited, allevents still caps at 100)
       │   ⚠️ total ≈ limit × source count, and that is the billable count
       ▼
6. Using meetup? country_code MUST match the location's country
       (Dubai → "ae", London → "gb", Mumbai → "in")
       ▼
7. Deliver to a connector? YES → mcpConnector + mcpTool + {message}
       │                          summary (1 call) / chunked / perEvent (1 per event!)
       │ NO  → set deliveryMode "none" (it defaults to summary)
       ▼
8. Sources run SEQUENTIALLY → run time is the SUM. Use the ASYNC endpoint
       ▼
[Execute]
       │
       ├──► SUCCEEDED, items > 0 ──► handle the merged contract:
       │        ALWAYS branch on `source` first
       │        "N/A" is a STRING, and on price/venue_address may mean
       │            "this source never provides it" — not a scrape failure
       │        `date` is NOT normalised → parse per source, never sort the merged set raw
       │        NO cross-platform dedup → dedupe on title + date yourself
       │
       └──► SUCCEEDED, items == 0 ──► READ THE PER-SOURCE LOG LINES:
                "Unknown source: '<x>' — skipping."   → fix the source value
                "[allevents] Input 'location' missing" → supply a city
                district selected but empty            → expected: no proxy support
                meetup empty                           → country_code mismatch
```

***

### 💰 Cost Control & Pricing Transparency

**Pay-per-event: one charge per record pushed to the dataset**, with no monthly subscription. The exact rate is on the Actor's **[Pricing tab](https://apify.com/techforce.global/universal-event-scraper)** in the Apify Store.

**Free Apify plans** are capped at **2 records per source**, so a five-source run returns up to 10 items — enough to validate a configuration before committing.

**Autonomous spend caps:** pass `maxTotalChargeUsd` as a query parameter on the run endpoint for a hard per-execution ceiling.

> ℹ️ **Always confirm the live per-event rate on the Actor’s [Pricing tab](https://apify.com/techforce.global/universal-event-scraper) before committing to a large run.** The Store listing is what bills you. Platform usage is charged on top and varies a lot by source: `eventseye` is HTTP-only and cheapest, `district` is the most expensive.

#### The multiplier to watch: `limit` is per source

| Sources selected | `limit` | Maximum records (= billable events) |
|:--|:--|:--|
| 1 (`eventseye`) | 50 | 50 |
| 2 | 50 | **100** |
| 4 | 50 | **200** |
| 5 | 50 | **250** |
| Any | `0` (unlimited) | **Unbounded** — `allevents` still caps at 100, the rest do not |
| Any | 2 (free plan) | 2 × source count |

**Platform usage is billed on top**, and it varies sharply by source:

| Source | Engine | Relative platform cost |
|:--|:--|:--|
| `eventseye` | httpx, no browser | 💚 **Lowest** — plain HTTP |
| `eventbrite` | Public browse JSON | 💚 Low |
| `allevents` | Playwright | 🟠 Browser cost |
| `meetup` | Playwright + GraphQL intercept | 🟠 Browser cost |
| `district` | Playwright + 5-page pool | 🔴 Highest — and usually returns nothing here |

**Cost levers:**

- **Drop `district`** unless you have a specific reason. It's the most expensive source and, without proxy support, typically yields nothing.
- **Start with `["eventseye"]` or `["eventbrite"]`** if cost matters — both avoid a browser entirely.
- **Trim the source list.** Every extra source multiplies both the record count and the run time.
- **Never use `limit: 0` without `maxTotalChargeUsd`.** It removes the cap completely.
- **Validate at `limit: 2`** before a full sweep.
- **Dedupe before charging your own downstream systems** — the same event on two platforms is billed twice here.
- **Prefer `summary`/`chunked` delivery** over `perEvent` — 1 connector call instead of hundreds.

***

### 🔍 Companion machine-readable files

| File | Purpose |
|---|---|
| `.actor/actor.json` | Identity, title, description, version, build tag, and the input / output / dataset schema references |
| `.actor/input_schema.json` | Authoritative typed input contract — the 5-value `source` enum, `location`, `limit`, the Meetup-only `country_code`/`eventType`, the 5 delivery fields, and the 5 inputs explicitly marked as not implemented |
| `.actor/dataset_schema.json` | Full JSON Schema for the record (a `oneOf` branch per source, so each platform's variant is described individually) plus the Console **Output** view |
| `.actor/output_schema.json` | Declares where results are stored |
| `my_actor/main.py` | All five scrapers, the five normalisers, the per-source free-plan cap, charging, and MCP delivery |
| `my_actor/connector.py` | MCP connector session handling through the Apify MCP Proxy |

Note: this Actor has **no `pay_per_event.json`** — the `premium-scrape` (and unused `basic-scrape`) events are configured on the Store listing.

If any table on this page disagrees with the schema files, **the schema files win**.

***

### 🛠️ Troubleshooting

| # | Symptom you see | Most likely cause | Fix |
|:--|:--|:--|:--|
| 1 | 0 items, log `Unknown source: '<x>' — skipping.` | Typo in a `source` value | Use `allevents`, `eventseye`, `district`, `meetup`, `eventbrite`. |
| 2 | Log `[allevents] Input 'location' is missing!` | `location` empty | Supply a city name. |
| 3 | Far more records than expected | `limit` is **per source** | Total ≈ limit × source count. Trim sources. |
| 4 | Exactly 2 per source | Free Apify plan cap | Upgrade; the log prints the upgrade banner. |
| 5 | `allevents` stopped at 100 with `limit: 0` | That source caps at 100 regardless | Documented behaviour. |
| 6 | `dateRange` had no effect | **Not implemented** | Filter the dataset's `date`, or use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper). |
| 7 | `keywords` had no effect | **Not implemented** | Filter the dataset's `title`. |
| 8 | Eventbrite ran despite `eventbriteSearch: false` | That toggle is inert | Remove `eventbrite` from `source`. |
| 9 | `district` returned nothing | **No proxy support**; District blocks datacenter IPs | Use [District.in Event Scraper](https://apify.com/techforce.global/district-event) with a residential-IN proxy. |
| 10 | `meetup` returned nothing | `country_code` doesn't match the city's country | Set it correctly — `ae` for Dubai, `gb` for London. |
| 11 | One source empty, others fine | No coverage for that city, or a layout change | Check that source's `[<source>]` log lines. |
| 12 | `price` always `"N/A"` | Only `district`, `meetup`, `eventbrite` populate it | Expected for `allevents`/`eventseye`. |
| 13 | `venue_address` always `"N/A"` | Only `eventseye`, `meetup`, `eventbrite` populate it | Expected for `allevents`/`district`. |
| 14 | Null checks never fire | Missing values are the **string** `"N/A"` | Compare against `"N/A"`. |
| 15 | Date parsing fails on some rows | `date` is **not** normalised across sources | Branch on `source` before parsing. |
| 16 | Sorting the merged set by date misorders it | Mixed formats — display strings vs ISO 8601 | Normalise per source first. |
| 17 | The same event appears twice | No cross-platform dedup | Dedupe on `title` + `date`. |
| 18 | Run takes several minutes | Sources run **sequentially**, several with browsers | Expected; use the async endpoint. |
| 19 | `HTTP 408` on `run-sync-get-dataset-items` | The synchronous endpoint has a hard **300-second** ceiling | Use async `POST /runs` → poll → fetch. |
| 20 | Log `Charge failed for '<event>'` | The billing call failed | The record was still saved — no data loss. |
| 21 | Only one city came back | `location` is a single string | Loop one run per city. |
| 22 | Hundreds of connector calls | `deliveryMode: "perEvent"` | Switch to `summary` or `chunked`. |
| 23 | Notion / Airtable timed out | One call carrying every event is too large | Use `deliveryMode: "chunked"`. |
| 24 | Slack / Notion received nothing | Delivery needs **both** `mcpConnector` and `mcpTool` | Set both. The dataset is still written in full. |
| 25 | Connector fired but the body is empty | `mcpArguments` did not reference `{message}` | Map `{message}` and populate `mcpMessageTemplate`. |
| 26 | Log `APIFY_MCP_PROXY_URL is not set` | You ran locally; connectors only resolve on the platform | Deploy with `apify push` and run on the platform. |
| 27 | `HTTP 401` / `403` from Apify | Missing, expired or malformed `APIFY_TOKEN` | Regenerate in **Apify Console → Settings → API & Integrations**. |
| 28 | `HTTP 429` from Apify | Apify account concurrency / rate limits | Retry with exponential backoff (2s → 4s → 8s). |

#### Known limitations

- **No proxy support.** There is no proxy input at all. This mainly hurts the `district` source, which needs a residential Indian IP to work.
- **`dateRange` and `keywords` are not implemented**, along with `eventbriteApiKey`, `eventbriteSearch` and `eventbriteErrorHandling`. All five are accepted and ignored.
- **`date` is not normalised** across sources — a display string on some, ISO 8601 on others.
- **No cross-platform deduplication**, so the same event on two platforms is two records.
- **Eight fields only** — no description, image, category, organiser, lineup, availability or ID.
- **One city per run**, and sources run sequentially, so wall-clock time is the sum of the selected sources.

#### Diagnostic checklist before opening an issue

1. **Single-source baseline.** `{"source":["eventseye"],"location":"dubai","limit":5,"deliveryMode":"none"}` — HTTP-only, no browser, fastest to prove the pipeline works.
2. **Items returned?** If yes, the pipeline is fine — the problem is specific to another source or city.
3. **Read the per-source log lines.** Each source logs `Running scraper: '<x>'` and its own `[<source>]` messages; that tells you which one produced nothing.
4. **Rule out the known-empty cases first.** `district` without a proxy, and `meetup` with a mismatched `country_code`, are the two most common "it returned nothing" reports.
5. **Confirm the counting model.** Unexpected volume is almost always `limit` being per-source rather than per-run.

If the issue survives all five steps, open an **Issues** ticket on the Actor page (or email support) with the **run ID**, the **exact input JSON**, and which `source` fell short.

### ❓ FAQ

#### Scope & sources

**What does "universal" actually mean here?**

Five platforms in one run, normalised to one record shape. It isn't a generic any-website event scraper — the five sources are fixed and each has purpose-built extraction.

**Do I need accounts or API keys for the sources?**

No. All five read publicly available listings. (The `eventbriteApiKey` field in the schema is a leftover and is not used.)

**Can I plug it into Claude, Cursor, or a LangChain agent?**

Yes — it is a native MCP tool:

`claude mcp add --transport http apify "https://mcp.apify.com/?tools=techforce.global/universal-event-scraper"`

**Which source should I pick if I only want one?**

Then use the dedicated Actor instead — [All Events](https://apify.com/techforce.global/all-events-scraper), [Eventbrite](https://apify.com/techforce.global/eventbrite-scraper), [District.in](https://apify.com/techforce.global/district-event) or [Events Eye](https://apify.com/techforce.global/events-eye-scraper). Each returns more fields and more options than the unified 8-field record.

**Why does the `district` source return nothing?**

District fingerprints the caller's IP and hides its location picker from datacenter ranges. This Actor has no proxy input, so it can't get past that. Use the [District.in Event Scraper](https://apify.com/techforce.global/district-event), which supports the residential-IN proxy that source requires.

**Why did Meetup return nothing for a real city?**

`country_code` has to match the city's country — it defaults to `in`. Dubai needs `ae`, London needs `gb`.

#### Filtering

**Can I filter by date or keyword?**

Not here. Both inputs exist in the schema but **no code reads them** — they're documented as not implemented. Use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper), which does filter by category, keyword and date range across six platforms, or filter this Actor's dataset afterwards.

**Can I scrape several cities in one run?**

No — `location` is a single string. Loop one run per city.

#### Output

**Is the output really identical across sources?**

Yes — all five normalisers emit exactly the same eight keys in the same order. What differs is which of them a given source can actually fill.

**Why is `price` always `"N/A"` for some sources?**

Because those sources don't provide it: `allevents` and `eventseye` hardcode `"N/A"`. Only `district`, `meetup` and `eventbrite` populate it. Same story for `venue_address`, which only `eventseye`, `meetup` and `eventbrite` fill.

**Why `"N/A"` instead of `null`?**

That's the Actor's convention for every absent value. Test `== "N/A"` rather than relying on null checks.

**Can I sort the merged set by date?**

Not on the raw value — `date` is not normalised. Meetup can return ISO 8601 with a timezone while AllEvents returns something like `Sat, 12 Sep, 7:00 pm`. Parse per `source` first.

**Will the same event appear twice?**

Yes, if two platforms list it. There's no cross-platform deduplication; dedupe on `title` + `date`.

#### Pricing

**How is this billed?**

Pay-per-event — one charge per record pushed. The current rate is on the Actor's Pricing tab. Apify platform usage is billed separately and depends heavily on which sources you pick (`eventseye` is HTTP-only and cheapest; `district` is the most expensive).

**How do I keep a run cheap?**

Fewer sources, prefer `eventseye`/`eventbrite`, drop `district`, and never use `limit: 0` without a spend cap.

**How do I guarantee an agent never overspends?**

Pass `maxTotalChargeUsd` as a query parameter on the run endpoint.

***

### 🔗 Related Actors

#### This Actor's place in the event family

| Actor | Sources | Filters? | Why pick it over this one |
|:--|:--|:--|:--|
| **This Actor** | **5 platforms** | ❌ None | One unified 8-field schema across five platforms in one run |
| [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) | **6 platforms** | ✅ **Category, keyword, date range** | ⭐ Same multi-platform idea **plus** the filtering this Actor lacks, with per-source limits |
| [All Events Scraper](https://apify.com/techforce.global/all-events-scraper) | allevents.in | ❌ | Depth on one platform — more fields than the unified record |
| [Eventbrite Scraper](https://apify.com/techforce.global/eventbrite-scraper) | eventbrite.com | ⚠️ Some | Depth on Eventbrite, incl. category and location handling |
| [District.in Event Scraper](https://apify.com/techforce.global/district-event) | district.in | ❌ | **Has the residential-proxy support District requires** |
| [Events Eye Scraper](https://apify.com/techforce.global/events-eye-scraper) | eventseye.com | ⚠️ Some | Depth on trade shows and exhibitions |
| [Eventim Scraper](https://apify.com/techforce.global/eventim-scraper) | eventim.de | ✅ Search, city, category, date | German ticketed events with **live price and stock** |
| [Ticketmaster FR/ES Scraper](https://apify.com/techforce.global/ticketmaster-fr-es-scraper) | Ticketmaster API | ⚠️ Category only | French and Spanish ticketed events via the official API |

> ⭐ **[Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) is the closer sibling — compare them before choosing.** It covers six platforms (including District) *and* takes a `category`, a free-text `search_query` and a `date_from`/`date_to` range, with a per-source limit and one standardised output schema. If you want filtering, go there.
>
> **Choose this Actor when** you specifically want these five sources normalised into one flat 8-field record and don't need filtering at the source.

#### Enrichment & downstream pipeline

| Actor | Use it for |
|:--|:--|
| [Google Maps Business Leads & Sales Intelligence](https://apify.com/techforce.global/google-maps-leads-sales-intelligence-tool) | Venue contact details — email, phone, website — which this Actor never returns |
| [Website Contact Scraper](https://apify.com/techforce.global/website-contact-scraper-emails-phone-numbers-social-links) | Contacts from an organiser's or venue's own website |
| [Hospitality Lead Scraper](https://apify.com/techforce.global/hospitality-lead-scraper) | Bars, hotels and restaurants near a venue, for event-driven local outreach |

#### Suggested pipeline patterns

**📊 Platform coverage audit**
Run all sources for one city → group by `source` → see which platform actually covers that market before committing to a single-source Actor.

**🌍 Mixed consumer + B2B feed**
`["allevents","eventseye"]` → one dataset holding both consumer events and trade shows, already in one shape.

**💸 Cheap-first two-pass**
Pass 1 `["eventseye","eventbrite"]` (no browsers) → if coverage is thin, pass 2 adds `["allevents","meetup"]`.

**🎯 Event-driven local sales**
Large upcoming events in a city → [Google Maps Business Leads](https://apify.com/techforce.global/google-maps-leads-sales-intelligence-tool) or [Hospitality Lead Scraper](https://apify.com/techforce.global/hospitality-lead-scraper) for nearby venues → pitch catering, transport or accommodation.

**🏢 Trade-show prospecting**
`["eventseye"]` per target city → [Website Contact Scraper](https://apify.com/techforce.global/website-contact-scraper-emails-phone-numbers-social-links) on exhibitor sites → a contactable exhibitor list.

> Browse all Actors by Techforce Global at **[scraper.techforce.global](https://scraper.techforce.global/)**.

***

### 🔐 Compliance & Data Privacy

This Actor is an independent tool. It is **not** affiliated with, endorsed by, or sponsored by AllEvents, EventsEye, District, Zomato, Meetup, Eventbrite, or any venue, organiser or performer named in its output. All trademarks are the property of their respective owners.

It reads **publicly available event listings** from five platforms. It does not log in, does not accept credentials, and never buys, reserves or contacts anything.

**The output is mostly commercial event data** — titles, venues, dates, prices, public URLs. Three things still deserve care:

- **Meetup and Eventbrite listings can carry personal data.** Community meetups are often organised by named individuals, and a group or event title can identify a person. Where that happens you are processing personal data under GDPR/UK GDPR and CCPA — have a lawful basis, set a retention period, and don't repurpose it as a marketing list simply because it was public.
- **Each source has its own terms of service, and you are bound by all five you enable.** They are not interchangeable: a use that's acceptable for a public trade-show directory may not be for a ticketing platform. Review the terms of each `source` you select.
- **Prices and availability are volatile and time-stamped by the run.** Republishing them as current commits you to a refresh cadence; always attribute the source and link through to `url` for the live listing. Note also that `price` is absent by design on two of the five sources, so a merged feed will have systematic gaps.

Because sources run sequentially at modest volume, the footprint on each site is light — don't defeat that by running many parallel executions against the same source and city. You remain responsible for complying with every enabled platform's terms of service and all applicable data-protection and consumer-protection law in the markets you operate in.

***

### 🆘 Support & Custom Pipeline Engineering

Need date and keyword filtering wired in, proxy support for the District source, cross-platform deduplication, additional platforms, or a scheduled multi-city event pipeline feeding your own systems?

- **Email**: bhavin.shah@techforceglobal.com
- **Custom Enterprise Integrations**: [Book a 15-Minute Technical Consultation](https://calendly.com/techforce-global/intro-meeting)
- **Maintained by**: [Techforce Global](https://www.techforceglobal.com) — Specialists in High-Performance Web Scrapers and Agentic Workflows.

***

Made with ❤️ by **[Techforce Global](https://www.techforceglobal.com)**
Specialists in High-Performance Web Scrapers and AI Automation.

***

### 🏷️ Structured data for search & AI discovery

`SoftwareApplication` describes the Actor; `FAQPage` wraps the FAQ above so those answers are
eligible for rich results, matching it question-for-question.

```json
{
    "@context": "https://schema.org/",
    "@type": "SoftwareApplication",
    "name": "Universal Event Scraper",
    "alternateName": "Universal Event Scraper — 5 Event Platforms, One Unified Schema",
    "applicationCategory": "BusinessApplication",
    "applicationSubCategory": "Web Scraping / Event Data Aggregation",
    "operatingSystem": "Cloud (Apify platform)",
    "description": "Scrapes events for one city from up to five platforms — AllEvents.in, EventsEye.com, District.in, Meetup.com and Eventbrite — in a single run, normalising every source into the same eight-field record: source, title, url, date, city, venue, venue_address and price. Each source uses a purpose-built engine (plain HTTP for the EventsEye trade-show directory, Playwright for the rendered listings, GraphQL interception for Meetup, public browse JSON for Eventbrite), and results can be delivered into Notion, Slack, Airtable, Google Sheets or any authorized MCP connector. Date-range and keyword filtering are not supported.",
    "url": "https://apify.com/techforce.global/universal-event-scraper",
    "author": {
        "@type": "Organization",
        "name": "Techforce Global",
        "url": "https://www.techforceglobal.com/"
    },
    "provider": {
        "@type": "Organization",
        "name": "Apify",
        "url": "https://apify.com/"
    },
    "offers": {
        "@type": "Offer",
        "priceCurrency": "USD",
        "category": "Pay per event",
        "description": "Pay-per-event, charged once per record returned. Apify platform usage is billed separately and varies by which sources you select. The current rate is listed on the Actor's Pricing tab.",
        "url": "https://apify.com/techforce.global/universal-event-scraper"
    },
    "featureList": [
        "No source accounts, API keys or credentials required",
        "Five platforms in one run, selectable individually",
        "One identical eight-field record from every source, in the same key order",
        "Per-source engines: HTTP, Playwright, GraphQL interception and public browse JSON",
        "Every row stamped with the source that produced it, so per-source quirks stay traceable",
        "Per-source result cap, with an explicit no-limit option for deliberate deep runs",
        "Charged once per record returned, so a source that yields nothing costs nothing in event charges",
        "Optional MCP delivery to Notion, Slack, Airtable, Google Sheets and more",
        "Chunked delivery mode keeps long event lists under connector size and timeout caps"
    ],
    "isAccessibleForFree": false,
    "softwareRequirements": "Apify account and API token",
    "softwareHelp": {
        "@type": "CreativeWork",
        "url": "https://apify.com/techforce.global/universal-event-scraper"
    },
    "keywords": "universal event scraper, multi-platform event data, AllEvents scraper, EventsEye scraper, District.in scraper, Meetup scraper, Eventbrite scraper, unified event schema, event aggregation, MCP connector"
}
```

```json
{
    "@context": "https://schema.org/",
    "@type": "FAQPage",
    "mainEntity": [
        {
            "@type": "Question",
            "name": "What does \"universal\" actually mean here?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Five platforms in one run, normalised to one record shape. It is not a generic any-website event scraper - the five sources are fixed and each has purpose-built extraction."
            }
        },
        {
            "@type": "Question",
            "name": "Do I need accounts or API keys for the sources?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "No. All five read publicly available listings. The eventbriteApiKey field in the schema is a leftover and is not used."
            }
        },
        {
            "@type": "Question",
            "name": "Can I plug it into Claude, Cursor, or a LangChain agent?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Yes - it is a native MCP tool. Add it with: claude mcp add --transport http apify \"https://mcp.apify.com/?tools=techforce.global/universal-event-scraper\""
            }
        },
        {
            "@type": "Question",
            "name": "Which source should I pick if I only want one?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Then use the dedicated Actor instead: techforce.global/all-events-scraper, techforce.global/eventbrite-scraper, techforce.global/district-event or techforce.global/events-eye-scraper. Each returns more fields and more options than the unified eight-field record."
            }
        },
        {
            "@type": "Question",
            "name": "Why does the district source return nothing?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "District fingerprints the caller's IP and hides its location picker from datacenter ranges. This Actor has no proxy input, so it cannot get past that. Use techforce.global/district-event, which supports the residential-IN proxy that source requires."
            }
        },
        {
            "@type": "Question",
            "name": "Why did Meetup return nothing for a real city?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "country_code has to match the city's country - it defaults to in. Dubai needs ae, London needs gb."
            }
        },
        {
            "@type": "Question",
            "name": "Can I filter by date or keyword?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Not here. Both inputs exist in the schema but no code reads them - they are documented as not implemented. Use techforce.global/smart-event-scraper, which does filter by category, keyword and date range across six platforms, or filter this Actor's dataset afterwards."
            }
        },
        {
            "@type": "Question",
            "name": "Can I scrape several cities in one run?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "No - location is a single string. Loop one run per city."
            }
        },
        {
            "@type": "Question",
            "name": "Is the output really identical across sources?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Yes - all five normalisers emit exactly the same eight keys in the same order. What differs is which of them a given source can actually fill."
            }
        },
        {
            "@type": "Question",
            "name": "Why is price always \"N/A\" for some sources?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Because those sources do not provide it: allevents and eventseye hardcode N/A. Only district, meetup and eventbrite populate it. Same story for venue_address, which only eventseye, meetup and eventbrite fill."
            }
        },
        {
            "@type": "Question",
            "name": "Why \"N/A\" instead of null?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "That is the Actor's convention for every absent value. Test for the string N/A rather than relying on null checks."
            }
        },
        {
            "@type": "Question",
            "name": "Can I sort the merged set by date?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Not on the raw value - date is not normalised. Meetup can return ISO 8601 with a timezone while AllEvents returns something like Sat, 12 Sep, 7:00 pm. Parse per source first."
            }
        },
        {
            "@type": "Question",
            "name": "Will the same event appear twice?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Yes, if two platforms list it. There is no cross-platform deduplication; dedupe on title plus date."
            }
        },
        {
            "@type": "Question",
            "name": "How is this billed?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Pay-per-event - one charge per record pushed. The current rate is on the Actor's Pricing tab. Apify platform usage is billed separately and depends heavily on which sources you pick: eventseye is HTTP-only and cheapest, district is the most expensive."
            }
        },
        {
            "@type": "Question",
            "name": "How do I keep a run cheap?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Fewer sources, prefer eventseye and eventbrite, drop district, and never use limit 0 without a spend cap."
            }
        },
        {
            "@type": "Question",
            "name": "How do I guarantee an agent never overspends?",
            "acceptedAnswer": {
                "@type": "Answer",
                "text": "Pass maxTotalChargeUsd as a query parameter on the run endpoint."
            }
        }
    ]
}
```

# Actor input Schema

## `source` (type: `array`):

Select one or more event sources to scrape

## `location` (type: `string`):

City name used by all scrapers (e.g. ahmedabad, mumbai, berlin).

## `limit` (type: `integer`):

Max events per source (1-50). Set to 0 for NO LIMIT, which bypasses the 50 ceiling entirely - use with a spend cap. Free Apify plans are capped at 2 per source regardless.

## `eventbriteApiKey` (type: `string`):

NOT CURRENTLY USED by the Actor. Kept for backward compatibility only - the Eventbrite source reads the public browse JSON and does not authenticate. Do not rely on this field.

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

NOT CURRENTLY IMPLEMENTED. No source filters by date - this value is accepted and ignored. Filter the dataset on the "date" field after the run instead.

## `keywords` (type: `string`):

NOT CURRENTLY IMPLEMENTED. No source filters by keyword - this value is accepted and ignored. Filter the dataset on the "title" field after the run instead.

## `eventbriteSearch` (type: `boolean`):

NOT CURRENTLY USED. Selecting "eventbrite" in Source is what enables the Eventbrite scraper; this toggle is ignored.

## `eventbriteErrorHandling` (type: `boolean`):

NOT CURRENTLY USED. Eventbrite errors are always caught and logged; this toggle is ignored.

## `mcpConnector` (type: `string`):

Optionally deliver the scraped events into a connector you have authorized — Notion, Slack, Airtable, Google Sheets, or any MCP-compatible connector. Leave empty to only save results to the dataset.

## `deliveryMode` (type: `string`):

How to deliver events to the connector: 'summary' (one call with all events listed), 'chunked' (split a long event list across a few calls/pages so services like Notion never time out), 'perEvent' (one call per scraped event), or 'none' (save to dataset only).

## `mcpTool` (type: `string`):

Name of the tool to call on the connector (e.g. 'create\_page' for Notion, 'send\_message' for Slack, 'append\_row' for Google Sheets). If unsure, run once with a connector selected — the log lists the connector's available tools.

## `mcpArguments` (type: `object`):

Arguments passed to the connector tool. String values support {placeholders}. In 'summary' mode: {location}, {sources} (comma-separated list of scraped sources), {eventCount}, {events} (all events, one per line) and {message} (the rendered template below). In 'chunked' mode: same as summary but {events} holds one part of the list and {part}/{partCount} give the 1-based part number and total (put these in a title, e.g. 'Events in {location} (part {part}/{partCount})'). In 'perEvent' mode: {source}, {title}, {url}, {date}, {city}, {venue}, {price}, {venue\_address} and {message}. Example for Slack: {"channel": "#events", "text": "{message}"}.

## `mcpMessageTemplate` (type: `string`):

Optional template rendered and exposed as the {message} placeholder in the tool arguments. Summary example: 'Scraped {eventCount} events in {location} from {sources}:\n\n{events}'. Per-event example: '{title} — {date} @ {venue}, {city} ({price}): {url}'.

## `country_code` (type: `string`):

ISO 3166-1 alpha-2 country code for the Meetup listing URL (e.g. 'in', 'us', 'gb', 'ae'). Must match the country your Location city is in, or Meetup returns nothing. Ignored by every other source.

## `eventType` (type: `string`):

Whether to list in-person or online Meetup events. Ignored by every other source.

## Actor input object example

```json
{
  "source": [
    "eventseye"
  ],
  "location": "Dubai",
  "limit": 50,
  "eventbriteApiKey": "KK3NE5IJEE7SSMPESE2D",
  "dateRange": "",
  "keywords": "",
  "eventbriteSearch": false,
  "eventbriteErrorHandling": true,
  "deliveryMode": "summary",
  "mcpTool": "",
  "mcpArguments": {},
  "mcpMessageTemplate": "",
  "country_code": "in",
  "eventType": "inPerson"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("techforce.global/universal-event-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("techforce.global/universal-event-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 '{}' |
apify call techforce.global/universal-event-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,techforce.global/universal-event-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/wV1GD5vLWadaCrxz3/builds/r21uS8nQ3sC6HjnmM/openapi.json
