# District.in Scraper - Indian City Events, Venues & Prices (`techforce.global/district-event`) Actor

Extract comprehensive event listings from District.in, including titles, dates, venues, prices, and direct URLs for any city. This actor handles dynamic content and infinite scrolling to deliver clean, structured data in JSON, CSV, or Excel formats, perfect for event aggregation and market research.

- **URL**: https://apify.com/techforce.global/district-event.md
- **Developed by:** [Techforce Global](https://apify.com/techforce.global) (community)
- **Categories:** Lead generation, Travel, Automation
- **Stats:** 28 total users, 2 monthly users, 90.5% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.20 / actor start

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?

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

## District.in Event Scraper — Indian City Events, Venues & Ticket Prices from Zomato's District

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange?logo=apify)](https://apify.com/techforce.global/district-event)
[![MCP Ready](https://img.shields.io/badge/MCP-Compatible-blue?logo=anthropic)](https://mcp.apify.com?tools=techforce.global/district-event)
[![Source](https://img.shields.io/badge/Source-district.in-red)](https://www.district.in/events/)
[![Geo](https://img.shields.io/badge/Coverage-India%20only-orange)](https://www.district.in/events/)
[![Proxy](https://img.shields.io/badge/Requires-RESIDENTIAL%20proxy%20\(IN\)-critical)](https://docs.apify.com/platform/proxy/residential-proxy)

> Scrape live event listings from **[district.in](https://www.district.in/events/)** — Zomato's events and ticketing platform — for any Indian city: **event title, date, venue, starting ticket price and the direct booking URL**. Pick a city the way a real user does (the Actor drives District's own location picker), then let the results flow into Notion, Slack, Airtable or Google Sheets through an MCP connector.

> ⚠️ **Read this before your first run: you need a RESIDENTIAL proxy with country IN.** District fingerprints the caller's IP. From a datacenter range it still serves the page shell but **silently withholds the location picker**, and from a non-Indian IP it serves an **empty event feed**. Both look like "this city has no events" but are neither. The input schema already defaults to `RESIDENTIAL` + `IN` — leave it that way. Residential proxies require a paid Apify plan.

***

#### Doing this by hand vs. this Actor

There is no District public API, so the realistic alternative is a person clicking through the site — or a naive scraper that gets quietly starved of data.

| Feature / Capability | Manual browsing / naive scraper | This Actor |
| :--- | :--- | :--- |
| **Getting past the IP fingerprint** | ⚠️ A datacenter IP loads the shell with **no location picker** — looks like a broken city name | ✅ Residential + IN by default, with **3 retries on a fresh exit IP** each time |
| **Diagnosing an empty result** | ❌ "No events" — cause unknown | ✅ Checks the **actual egress country** and tells you whether it's geo-gating, throttling, or a genuinely empty city |
| **City selection** | Manual click-through per city | ✅ Drives the real picker: search box first, then a scrolling dialog fallback |
| **Infinite scroll** | ⚠️ Manual scrolling, easy to stop early | ✅ Scrolls to exhaustion (up to 100 passes) with stall detection |
| **Dead-page detection** | ❌ Scrolls a blank grid indefinitely | ✅ Bails after **2 scrolls** if the grid never rendered, and retries on a new IP |
| **Separating events from noise** | ⚠️ Artist, venue-guide and category pages look like events and yield junk rows | ✅ Slug-shape filtering drops sub-pages, category listings and nav links |
| **Speed** | One page at a time | ✅ **5 concurrent browser pages**, images/fonts/trackers blocked |
| **Structured output** | Copy-paste | ✅ Clean dataset → JSON / CSV / Excel / XML |
| **Delivery to your tools** | ❌ Manual | ✅ Native **MCP connector** delivery — per event, chunked, or one digest |

***

### 🤖 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/district-event"
  one_line: "Scrape events for one Indian city from district.in (Zomato's events platform) - title, date, venue, starting price and booking URL - driving the site's own location picker over a residential Indian IP."
  primary_purpose: "discover"
  secondary_purpose: "deliver"
  entity: "ticketed and listed events (comedy, music, workshops, sports, theatre, nightlife) on district.in"
  data_source: "https://www.district.in/events/%20-%20rendered%20with%20Playwright%20(Chromium).%20No%20public%20API%20exists;%20JSON-LD%20embedded%20in%20each%20event%20page%20is%20preferred,%20with%20CSS%20selectors%20as%20fallback."
  geography: "India only. district.in serves its event feed exclusively to Indian IPs."
  invocation_modes: ["standard Actor run", "MCP tool"]
  hard_requirement:
    residential_proxy: >
      Apify Proxy with group RESIDENTIAL and country IN. On datacenter IPs District
      hides the location picker; from a non-Indian IP the event feed comes back
      empty. Requires a paid Apify plan. This is a functional requirement, not a
      performance tuning option.
  supported_tasks:
    - "list-events-for-one-indian-city"
    - "read-event-title-date-venue-and-starting-price"
    - "get-direct-district-booking-urls"
    - "monitor-a-city-event-feed-on-a-schedule"
    - "deliver-events-to-mcp-connector-or-crm"
  unsupported_tasks:
    - "book-reserve-or-buy-tickets"
    - "scrape-more-than-one-city-per-run"
    - "filter-by-category-date-range-or-price (no such input exists)"
    - "return-seat-maps-price-tiers-or-remaining-inventory"
    - "return-organizer-or-promoter-contact-details"
    - "return-event-descriptions-images-or-lineups"
    - "scrape-district-restaurant-dining-or-movie-listings"
    - "scrape-non-indian-cities"
    - "return-more-than-200-events-per-run"
    - "log-in-to-a-district-account"
  search_keywords:
    - "district.in scraper"
    - "zomato district events"
    - "indian event data"
    - "mumbai events api"
    - "bengaluru event listings"
    - "delhi events scraper"
    - "india ticket price data"
    - "comedy show listings india"
    - "event aggregator india"
    - "city event feed india"
  synonyms:
    - "zomato district event scraper"
    - "district events extractor"
    - "indian city event listings scraper"
  related_concepts:
    ["event discovery", "city event feeds", "ticket pricing research", "venue programming research", "event aggregation", "India market research"]
  input_entities:
    - "location (one Indian city; REQUIRED)"
    - "maxItems (1-200)"
    - "proxyConfiguration (RESIDENTIAL + IN strongly required)"
  output_entities: ["title", "date", "venue", "price", "event_url", "city"]
  input_rules:
    required_fields: ["location"]
    one_city_per_run: "there is no multi-city input - loop from your orchestration layer"
    max_items_format:
      type: "integer"
      range: [1, 200]
      default: 50
      hard_cap: 200
      free_plan_cap: 5
    no_filters: >
      There is no category, date-range, price or availability filter. The Actor
      returns whatever District's city feed shows, in feed order. Filter after the
      fact on the dataset.
  missing_value_convention:
    venue: "null when District did not expose a venue"
    date: "the string 'Open Dated' when no start date is published"
    price: "the string 'N.A.' when no starting price is published"
  limits:
    events_per_run: 200
    cities_per_run: 1
    free_plan_cap: 5
    concurrency: "5 browser pages"
    max_scrolls: 100
  pricing:
    model: "pay-per-result"
    note: "exact rate is configured on the Store listing - see the Actor's Pricing tab"
  when_to_use: >
    You want the current district.in event feed for one Indian city - to power an
    event listing, research venue programming and entry price points, or watch a
    city's feed on a schedule.
  when_not_to_use: >
    You need to book tickets, need category/date filtering at the source, need
    several cities in one run, need event descriptions or lineups, or you are
    outside India - and especially if you cannot use a residential proxy.
  alternatives:
    - "Use techforce.global/smart-event-scraper when you need category, keyword or date-range filtering - it applies those filters across six platforms INCLUDING District, in one run"
    - "Use techforce.global/all-events-scraper for multi-city global event discovery, including India, without a residential-proxy requirement"
    - "Use techforce.global/eventbrite-scraper for organizer-led events, workshops and meetups"
    - "Use techforce.global/eventim-scraper for German ticketed events with live stock and price signals"
    - "Use techforce.global/events-eye-scraper for trade shows and B2B exhibitions"
```

***

```json
{
    "@context": "https://schema.org/",
    "@type": "SoftwareApplication",
    "name": "District.in Event Scraper — Indian City Events, Venues & Ticket Prices",
    "applicationCategory": "BusinessApplication",
    "operatingSystem": "Cloud (Apify platform)",
    "description": "Scrapes live event listings from district.in (Zomato's events and ticketing platform) for one Indian city per run — event title, start date, venue, starting ticket price and the direct booking URL — by driving District's own location picker in a real browser over a residential Indian IP, reading each event page's embedded JSON-LD. Results are saved to a dataset and can be delivered into Notion, Slack, Airtable, Google Sheets or any authorized MCP connector. Requires Apify Proxy with the RESIDENTIAL group and country IN.",
    "url": "https://apify.com/techforce.global/district-event",
    "offers": {
                   "@type": "Offer",
                   "priceCurrency": "USD",
                   "description": "Pay-per-result, charged per event returned. The current rate is listed on the Actor's Pricing tab."
               }
}
```

***

### What this Actor does

- **Loads district.in's events page in a real Chromium browser** and picks your city through District's own **"Select Location"** dialog — search box first, scrolling list as a fallback — because the feed only populates once a location is set.
- **Scrolls the listing to exhaustion**, up to 100 passes with stall detection, so lazy-loaded cards keep arriving until the grid genuinely stops growing.
- **Filters the link soup down to real events.** `/events/` on District also hosts category listings (`…-book-tickets`), venue guides (`<venue>/venue-guide`), artist pages (`<name>/artist`) and nav links. Only `…-buy-tickets`-shaped detail slugs survive — a venue page has an `<h3>` too, so without this it would yield a junk row.
- **Reads each event page's embedded JSON-LD first** (`@type: Event`) and only falls back to CSS selectors, which is why the output survives front-end tweaks better than a pure selector scrape.
- **Runs 5 browser pages concurrently**, with images, media, fonts and 13 analytics/tracking hosts blocked — the single biggest speed win when every request goes through a residential exit IP.
- **Retries the whole load → pick city → harvest sequence up to 3 times**, rebuilding the proxy session each attempt so it lands on a **different exit IP** rather than re-failing on a bad one.
- **Explains empty results instead of shrugging.** When a harvest comes back empty it looks up the run's actual **egress country** and tells you whether it's geo-gating (`country != IN`), IP throttling, or a genuinely quiet city — each with the one action that applies.
- Optionally **pushes events to an authorized MCP connector** (Notion, Slack, Airtable, Sheets, or any MCP-compatible connector) per event, chunked, or as one digest. The dataset is always saved regardless of delivery settings.

> ℹ️ **What it does *not* do:** there is no category, date, price or availability filter, and no multi-city input. The Actor returns the city's feed as District orders it, and you filter afterwards. If you need filtering at the source, use **[Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper)** — it applies category, keyword and date-range filters across six platforms **including District**, in one run.

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

| Scenario | Fit |
| --- | --- |
| Powering an event listing or "what's on" page for an Indian city | ✅ Strong (the core use case) |
| Tracking what a city's District feed is promoting, on a schedule | ✅ Strong |
| Venue programming research — what's booked where in one city | ✅ Strong |
| Entry price-point research across venues or cities | ✅ Strong — `price` is the starting price |
| Building a multi-city India event dataset | ✅ Workable — one run per city, loop from your side |
| Feeding events into Notion / Airtable / Slack automatically | ✅ Strong (`deliveryMode`) |
| You cannot use a residential proxy (free plan) | ❌ **Blocker** — District hides the location picker from datacenter IPs |
| Booking or buying tickets | ❌ Read-only — never transacts |
| "Only comedy shows next weekend" | ❌ No category or date filter — use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper), which filters District plus 5 other sources |
| Several cities in one run | ❌ One city per run |
| Event descriptions, images, lineups, age limits | ❌ Five fields only — open `event_url` for detail |
| Seat maps, price tiers, tickets remaining | ❌ Not extracted |
| Organizer / promoter contact details | ❌ Not returned |
| District's dining, restaurant or movie sections | ❌ Events only |
| Cities outside India | ❌ District is India-only |
| More than 200 events in one run | ⚠️ Hard cap of 200 |

***

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

1. **The residential proxy is a functional requirement, not an optimisation.** On a datacenter IP District serves the page but hides the location picker, so the run ends with zero events and a "location picker never opened" message. Keep `apifyProxyGroups: ["RESIDENTIAL"]`.
2. **Set the proxy country to `IN`.** A residential IP outside India loads the page *and* opens the picker, then returns an **empty feed** — the most confusing failure this Actor has. The schema defaults to `IN`; don't drop it.
3. **Residential proxies need a paid Apify plan.** Without one this Actor cannot reliably work at all. That's a plan constraint, not a bug.
4. **`location` is required, and it's one city per run.** There is no multi-city input; loop from your orchestration layer.
5. **India only.** Non-Indian city names return nothing — District doesn't serve them.
6. **`maxItems` is 1–200, default 50**, clamped in code as well as in the schema. **Free Apify plans are capped at 5 events per run** — a successful run, not a failure.
7. **There are no content filters.** No category, no date range, no price ceiling, no "in stock". You get the city's feed in feed order. For **category, keyword or date-range filtering, use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper)**, which covers District plus five other platforms in one filtered run; otherwise filter the dataset afterwards.
8. **Zero events is a diagnosis, not a dead end.** Read the run's **status message** — the Actor distinguishes *never loaded* / *picker never opened* / *loaded but empty (with the egress country named)*.
9. **`venue` can be `null`.** It's the one genuinely nullable field.
10. **`date` falls back to the string `"Open Dated"`**, which means District published no start date — often a long-running or open-dated experience. It is not a parse failure.
11. **`price` falls back to the string `"N.A."`** and, when present, is a **display string including the currency symbol** (e.g. `"₹499"` or `"INR499"`) — not a number, and the **starting** price, not the price of any particular ticket.
12. **`date` format is inconsistent by design.** From JSON-LD it's ISO 8601; from the selector fallback it's whatever District printed (`"Sat, 12 Jul"`). Parse defensively.
13. **A run can return fewer than `maxItems`** simply because the city's feed is smaller. There is no total-match count.
14. **Runs are slow by nature** — a real browser over a residential IP, with a 90s navigation budget and up to 3 attempts. Budget minutes, not seconds, and don't use the 300s synchronous endpoint for large runs.
15. **`deliveryMode` defaults to `perEvent`, not `none`.** With a connector selected, a 200-event run makes 200 connector calls. Use `summary` or `chunked`.
16. **Use `chunked` for large sets going to Notion** — events are packed into ~1,800-character paragraphs and grouped into ~72,000-character calls so nothing times out.

```yaml
dependencies:
  - rule: "Apify Proxy group RESIDENTIAL is required for the location picker to appear"
    on_violation: "zero events, and a status message about the picker not opening"
  - rule: "proxy country must be IN"
    on_violation: "the picker opens but the event feed is empty; the log names the actual egress country"
  - rule: "residential proxy groups require a paid Apify plan"
    on_violation: "the Actor logs 'Could not set up Apify Proxy' and continues unproxied, which District blocks"
  - rule: "location is required and accepts exactly one Indian city"
    on_violation: "an omitted location falls back to Mumbai; a non-Indian city returns nothing"
  - rule: "maxItems is clamped to 200 in code, and to 5 on free Apify plans"
    note: "the schema maximum and the code cap agree at 200; the default is 50"
  - rule: "no category / date / price / availability filter exists"
    on_violation: "agents constructing filter arguments get them silently ignored - filter the dataset instead"
  - rule: "venue may be null; date may be 'Open Dated'; price may be 'N.A.'"
    on_violation: "null-safety errors, or 'N.A.' parsed as a number"
  - rule: "price is a display string with a currency symbol and is a STARTING price"
    on_violation: "quoting it as the ticket price, or doing arithmetic on it directly"
  - 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; PAID plan required for the residential proxy this Actor depends on"
  target_site: "no District account, login or cookie required - and none is accepted"
```

***

### 📥 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 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `location` | string | ✅ **Yes** | `"Mumbai"` | One Indian city name as District lists it | `"Bengaluru"` |
| `maxItems` | integer | ⬜ No | `50` | `1`–`200`; free plans capped at 5 | `200` |
| `proxyConfiguration` | object | ⬜ No | **`RESIDENTIAL` + `IN`** | Apify proxy editor object — see below | see below |

#### The proxy block — leave this alone

```json
{
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "IN"
  }
}
```

This is the schema default, and both parts matter:

| Setting | If you change it | Symptom |
| :--- | :--- | :--- |
| `apifyProxyGroups: ["RESIDENTIAL"]` | Datacenter / auto groups | Page loads, **location picker never appears**, zero events |
| `apifyProxyCountry: "IN"` | Any other country, or unset | Picker opens, **feed comes back empty** — the log names the actual egress country |
| `useApifyProxy: false` | No proxy at all | The container's own IP; District usually blocks it outright |

#### Cities District serves

District is India-only. Commonly available:

`Mumbai` · `Delhi` · `Bengaluru` · `Hyderabad` · `Pune` · `Chennai` · `Kolkata` · `Ahmedabad` · `Gurugram` · `Noida` · `Jaipur` · `Chandigarh` · `Kochi` · `Goa` · `Indore` · `Lucknow`

> ℹ️ **This list reflects the cities District has typically covered**, not a list read from the Actor’s schema. District adds and drops cities over time, so treat it as a guide and confirm against the site before relying on one in an automated pipeline.

#### Examples

**Standard run — one city**

```json
{
  "location": "Mumbai",
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "IN"
  },
  "deliveryMode": "none"
}
```

**Cheap validation run — confirm the proxy works before a full sweep**

```json
{
  "location": "Bengaluru",
  "maxItems": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "IN"
  },
  "deliveryMode": "none"
}
```

**Scheduled city feed — daily, delivered to Slack**

```json
{
  "location": "Delhi",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "IN"
  },
  "mcpConnector": "<your-authorized-slack-connector>",
  "deliveryMode": "summary",
  "mcpTool": "send_message",
  "mcpArguments": { "channel": "#india-events", "text": "{message}" },
  "mcpMessageTemplate": "🎟️ {eventCount} events live on District in {city}:\n\n{events}"
}
```

#### MCP Delivery Configuration (Optional)

Delivery pushes **event data into your own tools**. It never books, 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 | **`perEvent`** | `perEvent` (one call per event) · `summary` (one call with everything) · `chunked` (split across calls) · `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 connector's available tools. |
| `mcpArguments` | object | `{}` | Arguments passed to the tool. String leaves support `{placeholders}`. |
| `mcpMessageTemplate` | string | `""` | Template rendered and exposed to `mcpArguments` as `{message}`. |

**Per-event placeholders:** `{title}`, `{date}`, `{venue}`, `{price}`, `{event_url}`, `{city}`, `{eventCount}`, `{message}`.

**Summary / chunked placeholders:** `{city}`, `{eventCount}`, `{events}` (packed event blocks), `{message}` — plus `{part}` and `{partCount}` in chunked mode.

One Notion page per event:

```json
{
  "location": "Mumbai",
  "maxItems": 100,
  "mcpConnector": "<your-authorized-notion-connector>",
  "deliveryMode": "perEvent",
  "mcpTool": "notion-create-pages",
  "mcpArguments": {
    "parent": { "page_id": "<your-page-id>" },
    "pages": [
      {
        "properties": { "title": "{title}" },
        "content": "**When:** {date}\n**Where:** {venue}, {city}\n**From:** {price}\n\n[Book on District]({event_url})"
      }
    ]
  }
}
```

A large city feed split across Notion sub-pages:

```json
{
  "location": "Bengaluru",
  "maxItems": 200,
  "mcpConnector": "<your-authorized-notion-connector>",
  "deliveryMode": "chunked",
  "mcpTool": "notion-create-pages",
  "mcpArguments": {
    "parent": { "page_id": "<your-page-id>" },
    "pages": [
      {
        "properties": { "title": "District — {city} events (part {part}/{partCount})" },
        "content": "{events}"
      }
    ]
  }
}
```

One Google Sheets row per event:

```json
{
  "location": "Hyderabad",
  "maxItems": 200,
  "mcpConnector": "<your-authorized-sheets-connector>",
  "deliveryMode": "perEvent",
  "mcpTool": "append_row",
  "mcpArguments": {
    "values": ["{title}", "{date}", "{venue}", "{city}", "{price}", "{event_url}"]
  }
}
```

***

### 📤 Output Contract & Data Structure

**Storage:** Apify Dataset — one JSON object per event, pushed as each event page finishes.
**Pagination:** `limit` & `offset` on the dataset items endpoint.
**Duplicates:** event URLs are deduplicated within a run. Across scheduled runs, dedupe on `event_url`.

#### Event record — 6 fields

| Field | Type | Nullable / fallback | Meaning |
| --- | --- | --- | --- |
| `title` | string | never empty — an event with no title is **skipped**, not saved | Event name (JSON-LD `name`, else the page `<h3>`) |
| `date` | string | falls back to **`"Open Dated"`** | Start date. **ISO 8601 from JSON-LD**, or District's own display string from the selector fallback |
| `venue` | string | ⚠️ **`null`** when District exposed none | Venue name (JSON-LD `location.name`, else a selector) |
| `price` | string | falls back to **`"N.A."`** | **Starting** price as a display string with currency (e.g. `"₹499"`, `"INR499"`) — not a number |
| `event_url` | string | never empty | Direct District event page — **use as the dedupe key** |
| `city` | string | never empty | The city you asked for, echoed back for grouping |

> ⚠️ **Three different missing-value conventions in one record.** `venue` is `null`, `date` is the string `"Open Dated"`, and `price` is the string `"N.A."` (note the periods — not `"N/A"`). Handle each explicitly.

There is no description, image, category, lineup, age limit, seat map or inventory field. Open `event_url` for anything beyond these six.

#### Example dataset item

```json
{
  "title": "Zakir Khan — Papa Yaar Tour",
  "date": "2026-09-12T20:00:00+05:30",
  "venue": "Jio World Garden, BKC",
  "price": "₹999",
  "event_url": "https://www.district.in/events/zakir-khan-papa-yaar-tour-sep12-2026-buy-tickets",
  "city": "Mumbai"
}
```

An open-dated experience with no published price:

```json
{
  "title": "Sky Jumper Trampoline Park — Day Pass",
  "date": "Open Dated",
  "venue": null,
  "price": "N.A.",
  "event_url": "https://www.district.in/events/sky-jumper-trampoline-park-buy-tickets",
  "city": "Mumbai"
}
```

#### Parsing price and date safely

```python
import re
from datetime import datetime

def price_value(event):
    raw = event.get("price") or ""
    if raw in ("", "N.A."):
        return None                                  # not published - NOT free
    digits = re.sub(r"[^\d.]", "", raw)
    return float(digits) if digits else None

def start_datetime(event):
    raw = event.get("date") or ""
    if raw == "Open Dated":
        return None                                  # no start date published
    try:
        return datetime.fromisoformat(raw)           # JSON-LD path
    except ValueError:
        return None                                  # District display string - keep as text
```

#### What a successful run looks like

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

`itemCount` below `maxItems` is normal — the city's feed was simply smaller. `itemCount: 0` is a **valid outcome with a specific cause**, and this Actor names it in the run's **status message**:

| Status message | Meaning | Action |
| :--- | :--- | :--- |
| `Could not load https://www.district.in/events/ after 3 attempts …` | District never responded on any attempt | Re-run; confirm RESIDENTIAL + IN |
| `District blocked this run's IP, so the '<city>' location picker never loaded.` | **Datacenter IP** — the classic misconfiguration | Set `apifyProxyGroups: ["RESIDENTIAL"]` |
| `District did not show the location picker for '<city>' even on a residential proxy.` | Unlucky or rate-limited residential IP | Re-run; try another country/session |
| `District returned no events because this run's traffic exited from <XX>, not India.` | **Wrong egress country** | Set `apifyProxyCountry: "IN"` |
| `District loaded but returned no events for '<city>'. The exit IP is likely rate-limited …` | Picker worked, feed empty | Re-run; verify the city has events on the site |

Useful log lines during a healthy run: `✓ Loaded …`, `✓ City '<city>' selected successfully`, `Loaded events: N` (every 5 scrolls), `📊 Total valid event URLs found: N`, `💾 Saved [n/max]: <title>`.

***

### ▶️ Invocation & Integration

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

#### Apify Console

Open the Actor → **Input** tab → type a **Location** (one Indian city) → set **Max Items** → **leave Proxy Configuration on `RESIDENTIAL` + `IN`** → *(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/district-event"
```

#### Python (apify-client)

```python
import os, re
from apify_client import ApifyClient

client = ApifyClient(os.getenv("APIFY_TOKEN"))

IN_RESIDENTIAL = {
    "useApifyProxy": True,
    "apifyProxyGroups": ["RESIDENTIAL"],   # required - District hides the picker otherwise
    "apifyProxyCountry": "IN",             # required - non-IN exits get an empty feed
}

run = client.actor("techforce.global/district-event").call(run_input={
    "location": "Mumbai",
    "maxItems": 200,                       # ceiling; free plans stop at 5
    "proxyConfiguration": IN_RESIDENTIAL,
    "deliveryMode": "none",
})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(f'{len(items)} events — run status message: {run.get("statusMessage")}')

def price_value(event):
    raw = event.get("price") or ""
    if raw in ("", "N.A."):
        return None
    digits = re.sub(r"[^\d.]", "", raw)
    return float(digits) if digits else None

## All three fallbacks matter: venue is null, date is "Open Dated", price is "N.A.".
priced = [(e, price_value(e)) for e in items]
cheapest = sorted((p for p in priced if p[1] is not None), key=lambda p: p[1])[:5]
for event, value in cheapest:
    venue = event.get("venue") or "(venue not listed)"
    print(f'{value:>8.0f}  {event["date"]:<28} {event["title"][:45]}')
    print(f'          {venue}  ·  {event["event_url"]}')

print(f'{sum(1 for _, v in priced if v is None)} events published no starting price')
print(f'{sum(1 for e in items if e.get("venue") is None)} events with no venue')
print(f'{sum(1 for e in items if e.get("date") == "Open Dated")} open-dated events')
```

**Multi-city sweep** — one run per city, because there is no multi-city input:

```python
CITIES = ["Mumbai", "Delhi", "Bengaluru", "Hyderabad", "Pune"]
all_events = []

for city in CITIES:
    run = client.actor("techforce.global/district-event").call(run_input={
        "location": city,
        "maxItems": 200,
        "proxyConfiguration": IN_RESIDENTIAL,
        "deliveryMode": "none",
    })
    rows = client.dataset(run["defaultDatasetId"]).list_items().items
    if not rows:
        # Read the status message: it distinguishes a blocked IP from an empty city.
        print(f'{city}: 0 events — {run.get("statusMessage")}')
    all_events.extend(rows)

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 run = await client.actor('techforce.global/district-event').call({
  location: 'Bengaluru',
  maxItems: 200,
  proxyConfiguration: {
    useApifyProxy: true,
    apifyProxyGroups: ['RESIDENTIAL'],
    apifyProxyCountry: 'IN',
  },
  deliveryMode: 'none',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();

// venue is genuinely null; date and price use string sentinels instead.
const dated = items.filter((e) => e.date !== 'Open Dated');
const withVenue = items.filter((e) => e.venue !== null);
const priced = items.filter((e) => e.price !== 'N.A.');
console.log(`${items.length} events · ${dated.length} dated · ${withVenue.length} with venue · ${priced.length} priced`);
```

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

Runs take minutes — a real browser over a residential IP, with up to 3 load attempts. **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 '{"location":"Mumbai","maxItems":200,"proxyConfiguration":{"useApifyProxy":true,"apifyProxyGroups":["RESIDENTIAL"],"apifyProxyCountry":"IN"},"deliveryMode":"none"}' \
  'https://api.apify.com/v2/acts/techforce.global~district-event/runs?maxTotalChargeUsd=1.00'

## 2. Poll — and read statusMessage, not just status
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 (small runs only)

```bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{"location":"Mumbai","maxItems":10,"proxyConfiguration":{"useApifyProxy":true,"apifyProxyGroups":["RESIDENTIAL"],"apifyProxyCountry":"IN"},"deliveryMode":"none"}' \
  'https://api.apify.com/v2/acts/techforce.global~district-event/run-sync-get-dataset-items'
```

#### Scheduling & workflow tools

A daily or twice-daily Apify **schedule** per city is the natural pattern. Consume the dataset from n8n, Make, Zapier or a webhook — or skip the glue and let `mcpConnector` deliver into Slack or Notion directly.

***

### ⚠️ 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 | Browser runs over a residential IP routinely exceed 300s | **Reconfigure:** use async `POST /runs` + polling. |
| `MISSING_LOCATION` | Input validation rejection | `location` is required | **Modify Input:** send one Indian city. |
| `DATACENTER_IP_BLOCKED` | 0 items; status message `District blocked this run's IP, so the '<city>' location picker never loaded.` | Not on a residential group — District hides the picker | **Reconfigure:** `apifyProxyGroups: ["RESIDENTIAL"]`. |
| `WRONG_EGRESS_COUNTRY` | 0 items; status message names a country other than India | Residential exit IP outside India — District serves an empty feed | **Reconfigure:** `apifyProxyCountry: "IN"`. |
| `PROXY_UNAVAILABLE` | Log `Could not set up Apify Proxy (…). Continuing without a proxy` | Residential groups require a paid Apify plan | **Prompt User:** upgrade; this Actor cannot work reliably without it. |
| `NAV_FAILED` | 0 items; status message `Could not load https://www.district.in/events/ after 3 attempts` | District never responded — throttling or a dead exit IP | **Retry** once after a few minutes with RESIDENTIAL + IN. |
| `PICKER_BLOCKED_RESIDENTIAL` | 0 items; status message says the picker didn't open **even on a residential proxy** | Unlucky or rate-limited residential IP | **Retry:** a new run gets a different exit IP. |
| `EMPTY_FEED_THROTTLED` | 0 items; status message says loaded but empty, egress country **is** IN | District throttling this exit IP | **Retry**, then verify the city has events on the site. |
| `GRID_NEVER_RENDERED` | Log `Only N '/events/' link(s) after 3 scrolls` | Page shell served without the event grid | **Not actionable in-run** — the Actor already retries on a fresh IP. |
| `WRONG_CITY_SUSPECTED` | Agent decides to "fix the spelling" after a zero-event run | Almost never the cause — it's the IP | **Do not** re-spell the city first; fix the proxy, then re-check the city. |
| `NON_INDIAN_CITY` | 0 items with a correct residential IN proxy | District is India-only | **Abort & Prompt User:** Indian cities only. |
| `FREE_PLAN_CAP` | `itemCount` stops at 5, upgrade banner in the log | Free Apify plan caps the run at 5 events | **Prompt User:** upgrade for full-volume runs. |
| `SIZE_CLAMPED` | Asked for more than 200, got 200 | `maxItems` is hard-capped at 200 | **Fix Expectation:** 200 per run per city. |
| `FILTER_UNSUPPORTED` | Agent passes `category`, `dateFrom`, `maxPrice`, … | No such inputs exist on this Actor | **Re-route:** [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) for category / keyword / date-range (District included as a source). For price, run unfiltered and filter the dataset. |
| `MULTI_CITY_UNSUPPORTED` | Agent passes a city array | One city per run | **Loop:** one run per city. |
| `VENUE_NULL` | `TypeError` on `venue` | `venue` is genuinely `null` sometimes | **Fix Code:** null-guard it. |
| `DATE_OPEN_DATED` | Date parsing fails | `date` is the string `"Open Dated"` | **Fix Code:** treat as "no start date", not an error. |
| `DATE_FORMAT_MIXED` | Some dates parse as ISO, others don't | JSON-LD gives ISO 8601; the selector fallback gives a display string | **Fix Code:** try ISO, fall back to keeping the text. |
| `PRICE_NOT_NUMERIC` | `NaN` from arithmetic on `price` | `price` is `"₹499"` or `"N.A."` | **Fix Code:** strip non-digits; treat `"N.A."` as unknown — **not** free. |
| `PRICE_MISREAD` | Quoted price doesn't match checkout | `price` is the **starting** price | **Fix Expectation:** present it as "from ₹X". |
| `NO_EVENT_DETAIL` | No description, image, lineup or category | Six fields only | **Fix Expectation:** open `event_url`. |
| `NO_INVENTORY_DATA` | No seat map or tickets-remaining | Not extracted | **Fix Expectation:** open `event_url`. |
| `WRONG_VERTICAL` | User asked about District dining / restaurants / movies | This Actor scrapes `/events/` only | **Abort & Prompt User:** events only. |
| `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` | Up to 200 connector calls per run | `deliveryMode` defaults to `perEvent` | **Reconfigure:** use `summary` or `chunked`. |
| `DELIVERY_TIMEOUT` | Notion / Airtable times out on one call | One payload carrying every event is too large | **Reconfigure:** `deliveryMode: "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 `maxItems` reached | `maxTotalChargeUsd` spend threshold reached | **Prompt User:** confirm a higher spend cap. |

> **Reading run status:** `SUCCEEDED` with an empty dataset is a valid outcome here, not a failure — only a `FAILED` status is a platform-level problem. Every Actor-side row, quoted message and field condition in the table above comes from this Actor’s own behaviour; the `401`/`408`/`429` rows are standard Apify API responses.

***

### 🗣️ Natural Language → Actor Mapping

| User says | Intent | Constructed Actor input |
| --- | --- | --- |
| "What's happening in Mumbai this week?" | City discovery | `{"location":"Mumbai","maxItems":200}` + residential IN proxy |
| "Show me events in Bengaluru" | City discovery | `{"location":"Bengaluru","maxItems":200}` |
| "Comedy shows in Delhi" | Category intent | ⚠️ No category filter — ➡️ [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper), or run `{"location":"Delhi"}` and filter `title` afterwards |
| "Cheap events in Pune under ₹500" | Price intent | ⚠️ No price filter anywhere — run, then filter parsed `price` |
| "Events this weekend in Hyderabad" | Date intent | ⚠️ No date filter — ➡️ [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) (`date_from`/`date_to`), or filter parsed `date` afterwards |
| "Build me an India-wide event feed" | Multi-city | Loop one run per city: Mumbai, Delhi, Bengaluru, Hyderabad, Pune, … |
| "Track what District is promoting in Chennai" | Monitoring | Schedule `{"location":"Chennai","maxItems":100}` daily |
| "What's on at a particular venue?" | Venue research | Run the city, then filter on `venue` |
| "Compare ticket prices between Mumbai and Delhi" | Pricing research | One run per city, compare parsed `price` |
| "Push tonight's events into Notion" | Delivery | `{"mcpConnector":"notion","deliveryMode":"chunked","mcpTool":"notion-create-pages"}` |
| "Post the Bengaluru feed to Slack every morning" | Digest delivery | Schedule + `{"deliveryMode":"summary","mcpTool":"send_message"}` |
| "Why did this return zero events?" | Diagnosis | Read the run's **status message** — it names the exact cause |
| "Book me two tickets" | Transaction | ❌ Read-only — open `event_url` |
| "How many tickets are left?" | Inventory | ❌ Not extracted |
| "Give me the full event description and lineup" | Detail | ❌ Six fields only — open `event_url` |
| "Same thing for Dubai / Singapore" | Non-India | ❌ District is India-only |
| "Get me the organizer's phone number" | Contact data | ❌ Not returned |
| "Show me restaurants on District" | Wrong vertical | ❌ Events only |

**Should NOT route here:** booking, reserving or buying tickets · **category, keyword or date-range filtering at the source** (➡️ [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper)) · several cities in one run · event descriptions, images, lineups or age limits · seat maps or remaining inventory · organizer / promoter contact details · District's dining, restaurant or movie sections · any city outside India · any run where a residential proxy is unavailable.

***

### 🧭 Agent Execution & Routing Logic

```text
[Input User Query]
       │
       ▼
1. Is this about events in an INDIAN city on district.in?   NO → Route elsewhere
       │ YES                                                     (All Events Scraper covers other geographies)
       ▼
2. Does the user want to TRANSACT (book / buy / reserve)?   YES → Abort. Read-only. Hand them event_url
       │ NO
       ▼
2b. Did they ask for a CATEGORY or DATE-RANGE filter?
       │ YES → Route to Smart Event Scraper: it filters by category, keyword and
       │        date range across six platforms INCLUDING District, in one run
       │ NO
       ▼
3. ── PRECONDITION ── Is a RESIDENTIAL proxy available (paid Apify plan)?
       │ NO  → Abort and say why: District hides the location picker from
       │        datacenter IPs, so the run would return zero events
       │ YES → proxyConfiguration = {RESIDENTIAL, country IN}   ← BOTH parts required
       ▼
4. Still here with a filter requirement (e.g. price)?
       │ YES → no Actor filters on price. Run unfiltered, then filter the dataset
       ▼
5. One city, or several?
       ├── one      → location = "<city>"
       └── several  → ONE RUN PER CITY, looped from the orchestration layer
       ▼
6. Set maxItems ≤ 200 (default 50, free plans 5)
       ▼
7. First run against this city/proxy combination?
       │ YES → validate with maxItems: 5 before a full sweep
       ▼
8. Deliver to a connector? YES → mcpConnector + mcpTool + {message}
       │                          summary (1 call) / chunked (large feeds) / perEvent (≤200 calls)
       │ NO  → set deliveryMode "none" (it defaults to perEvent)
       ▼
9. Use the ASYNC endpoint — browser + residential IP runs regularly exceed 300s
       ▼
[Execute]
       │
       ├──► SUCCEEDED, items > 0 ──► handle the field contract:
       │        venue may be null            → null-guard
       │        date may be "Open Dated"     → no start date, not an error
       │        date format is MIXED         → try ISO 8601, else keep the text
       │        price may be "N.A."          → unknown, NOT free
       │        price is a STARTING price with a currency symbol → strip before arithmetic
       │        dedupe on event_url
       │
       └──► SUCCEEDED, items == 0 ──► READ THE STATUS MESSAGE. It names the cause:
                "...location picker never loaded"        → NOT residential → fix the group
                "...exited from XX, not India"           → fix the country to IN
                "...even on a residential proxy"         → unlucky IP → re-run
                "Could not load ... after 3 attempts"    → District throttling → wait, re-run
                "...loaded but returned no events"       → re-run, then verify the city
           DO NOT start by second-guessing the city name — that is almost never it.
```

***

### 💰 Cost Control & Pricing Transparency

**Pay-per-result: you are charged per event returned, with no monthly subscription.** The exact rate is on the Actor's **[Pricing tab](https://apify.com/techforce.global/district-event)** in the Apify Store.

**Free Apify plans** are capped at 5 events per run — but note that a free plan also has **no residential proxy access**, which this Actor functionally requires. Treat a paid plan as the entry requirement.

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

> ℹ️ **Always confirm the live rate on the Actor’s [Apify Store page](https://apify.com/techforce.global/district-event) before committing to a large run.** The Store listing is what bills you; the figures above are a worked example at the advertised rate. Apify platform usage is billed separately.

**Platform usage is billed on top, and here it is the cost that actually bites.** Two multipliers stack:

| Cost driver | Why it matters on this Actor |
| :--- | :--- |
| **A real Chromium browser** | 5 concurrent pages, up to 100 scroll passes, one page load per event |
| **Residential proxy GB** | Metered per gigabyte, and every request in the run goes through it |
| **Up to 3 load attempts** | A blocked or throttled IP can cost 3 × a 90-second navigation before any event is scraped |
| **A failed run still costs usage** | Proxy traffic and compute are consumed even when the result set is empty |

The Actor already mitigates the biggest slice: **images, media, fonts and 13 analytics/tracking hosts are blocked**, and it waits for `domcontentloaded` rather than full `load`.

**Cost levers:**

- **Validate with `maxItems: 5` on any new city or proxy setup.** A misconfigured proxy costs a full run's proxy traffic for zero events — the cheapest possible way to discover that is a 5-item run.
- **Set `maxItems` to what you'll actually use.** Every event is a separate page load over a residential IP.
- **Don't over-schedule.** A city feed doesn't turn over hourly; daily or twice-daily captures the same information at a fraction of the proxy spend.
- **Prefer `summary`/`chunked` delivery** over `perEvent` — 1 connector call instead of up to 200.
- **Cap the spend** with `maxTotalChargeUsd` on any agent-driven run.

***

### 🔍 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 — `location` (required), `maxItems` (1–200), the **RESIDENTIAL + IN proxy default**, and the 5 delivery fields |
| `.actor/dataset_schema.json` | Console **Output** tab view — title, date, venue, price, event URL, city (with column labels) |
| `.actor/output_schema.json` | Declares where results are stored |
| `src/main.py` | Proxy wiring, retry/navigation logic, city picker, scroll harvesting, event-URL filtering, JSON-LD-first extraction, page pool, failure diagnostics, MCP delivery |
| `src/connector.py` | MCP connector session handling through the Apify MCP Proxy |
| `Dockerfile` | Built on `apify/actor-python-playwright:3.12` — the browser is required here, not incidental |

Note: this Actor has **no `pay_per_event.json`** and makes no `Actor.charge()` calls — billing is configured entirely on the Store listing.

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

***

### 🛠️ Troubleshooting

**Start here: over 90% of "no events" reports are the proxy, not the city.** Read the run's status message before changing anything else.

| # | Symptom you see | Most likely cause | Fix |
| :-- | :--- | :--- | :--- |
| 1 | 0 events; status message says the **location picker never loaded** | You are **not** on a residential proxy — District hides the picker from datacenter IPs | Set `apifyProxyGroups: ["RESIDENTIAL"]`. |
| 2 | 0 events; status message names a country **other than India** | The residential exit IP is outside India; District serves it an empty feed | Set `apifyProxyCountry: "IN"`. |
| 3 | 0 events; picker didn't open **even on residential** | Unlucky or rate-limited exit IP | Re-run — each run gets a different IP. |
| 4 | 0 events; `Could not load … after 3 attempts` | District never responded | Wait a few minutes and re-run. |
| 5 | 0 events; loaded and picker worked, feed empty, egress **is** IN | District throttling this IP | Re-run, then check the city on the site. |
| 6 | Log `Could not set up Apify Proxy … Continuing without a proxy` | Residential groups need a paid Apify plan | Upgrade — the Actor can't work reliably without it. |
| 7 | You changed the city spelling and it still returns 0 | The city name is almost never the cause | Fix the proxy first (rows 1–2), *then* revisit the city. |
| 8 | Exactly 5 events every run | Free Apify plan cap | Upgrade; the log prints the upgrade banner. |
| 9 | Asked for 500, got 200 | `maxItems` is hard-capped at 200 | 200 per run per city; loop for more. |
| 10 | Fewer events than `maxItems` | The city's feed is smaller | Expected — there's no total-match count. |
| 11 | Log `Only N '/events/' link(s) after 3 scrolls` | Page shell rendered without the event grid | The Actor already retries on a fresh IP; if all 3 fail see rows 1–2. |
| 12 | `TypeError` on `venue` | `venue` is genuinely `null` sometimes | Null-guard it. |
| 13 | `date` is `"Open Dated"` | District published no start date | Not an error — open-dated experience. |
| 14 | Some dates parse, others don't | JSON-LD gives ISO 8601; the fallback gives a display string | Try ISO, else keep the text. |
| 15 | `price` is `"N.A."` | No starting price published | **Not free.** Treat as unknown. |
| 16 | Arithmetic on `price` gives `NaN` | It's `"₹499"` — a display string | Strip non-digits first. |
| 17 | Quoted price doesn't match checkout | `price` is the **starting** price | Present as "from ₹X". |
| 18 | No description, image, category or lineup | Six fields only | Open `event_url`. |
| 19 | Category / date / price filter ignored | Those inputs don't exist on this Actor | Use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) for category / keyword / date range; filter the dataset for price. |
| 20 | Only one city came back | One city per run | Loop one run per city. |
| 21 | Runs take several minutes | Real browser + residential IP + up to 3 attempts | Expected; use the async endpoint. |
| 22 | `HTTP 408` on `run-sync-get-dataset-items` | The synchronous endpoint has a hard **300-second** ceiling | Use async `POST /runs` → poll → fetch. |
| 23 | Up to 200 connector calls per run | `deliveryMode` defaults to `perEvent` | Switch to `summary` or `chunked`. |
| 24 | Notion / Airtable timed out | One call carrying every event is too large | Use `deliveryMode: "chunked"`. |
| 25 | Slack / Notion received nothing | Delivery needs **both** `mcpConnector` and `mcpTool` | Set both. The dataset is still written in full. |
| 26 | Connector fired but the message body is empty | `mcpArguments` did not reference `{message}` | Map `{message}` in `mcpArguments` and populate `mcpMessageTemplate`. |
| 27 | 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. |
| 28 | `HTTP 401` / `403` from Apify | Missing, expired or malformed `APIFY_TOKEN` | Regenerate in **Apify Console → Settings → API & Integrations**. |
| 29 | `HTTP 429` from Apify | Apify account concurrency / rate limits | Retry with exponential backoff (2s → 4s → 8s). |

#### Diagnostic checklist before opening an issue

1. **Read the status message.** This Actor writes a terminal status message that names the exact failure. Everything below is only for when that isn't enough.
2. **Confirm both proxy settings.** `apifyProxyGroups: ["RESIDENTIAL"]` **and** `apifyProxyCountry: "IN"`. One without the other still fails, differently.
3. **Confirm you're on a paid plan.** Residential proxy access is a paid feature; without it the log says `Could not set up Apify Proxy`.
4. **Baseline run.** `{"location":"Mumbai","maxItems":5}` with the default proxy block. Mumbai is District's largest market — if it returns nothing, the problem is the proxy, not the city.
5. **Re-run once.** Each run gets a different exit IP; a single unlucky IP is a common and self-correcting cause.

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 the **status message** you saw.

### ❓ FAQ

#### Setup & access

**Why do I need a residential proxy? Can I skip it?**

No, not in practice. District fingerprints the caller's IP: from a datacenter range it serves the page shell but **withholds the "Select Location" dialog**, and the event feed never populates without a location. Residential access requires a paid Apify plan — that's the real entry requirement for this Actor.

**Why does the proxy country have to be IN?**

district.in serves its event feed only to Indian IPs. From a residential IP elsewhere the page loads, the picker opens, and the feed comes back **empty** — indistinguishable from a quiet city unless you know to look. The Actor checks the actual egress country when a harvest is empty and tells you.

**Do I need a District account or login?**

No — and none is accepted. Only public listing pages are read.

**Is there a District API I could use instead?**

Not a public one. That's why this Actor drives a real browser and prefers each event page's embedded JSON-LD, which is the most stable structured data District exposes.

**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/district-event"`

#### Coverage & filtering

**Which cities work?**

Indian cities District serves — Mumbai, Delhi, Bengaluru, Hyderabad, Pune, Chennai, Kolkata, Ahmedabad and others. `location` is free text, not an enum, so check [district.in/events](https://www.district.in/events/) if a city returns nothing on a correctly configured proxy.

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

No — one city per run. Loop from your orchestration layer; the Python example above shows the pattern.

**Can I filter by category, date or price?**

Not at the source — those inputs don't exist here. Use **[Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper)** instead: it takes a `category`, a free-text `search_query` and a `date_from`/`date_to` range, and applies them across six platforms **including District** in one run. Otherwise, run this Actor unfiltered and filter the dataset — note that no Actor in the family filters on price, since District only publishes a starting price.

**Does it get every event in the city?**

It scrolls the feed to exhaustion and takes up to `maxItems` (200 max). Whether that's "every event" depends on what District's feed surfaces for that city — treat it as the public feed, not a guaranteed complete inventory.

#### Output

**Why is `venue` sometimes `null`?**

Because District didn't expose one on that event page. It's the only genuinely nullable field.

**What does `date: "Open Dated"` mean?**

No start date was published — typical for long-running experiences and attractions rather than one-night shows.

**What does `price: "N.A."` mean?**

No starting price was published. It does **not** mean free.

**Is `price` the price I'll pay?**

It's the **starting** price for the event, as a display string with the currency symbol. Actual checkout depends on tier, seat and fees — present it as "from ₹X".

**Why are the date formats inconsistent?**

JSON-LD gives ISO 8601; when JSON-LD is absent the selector fallback returns District's own display string. Parse ISO first and keep the raw text otherwise.

**Can I get descriptions, images, lineups or seat availability?**

No — the output is six fields. Open `event_url` for anything more.

#### Pricing

**How is this billed?**

Pay-per-result — charged per event returned, with no monthly subscription. The current rate is on the Actor's Pricing tab.

**Why is platform usage higher than for other scrapers?**

Because a real browser and residential proxy traffic are both required here. The Actor blocks images, fonts, media and trackers to cut it down, but it can't be as cheap as an HTTP-only scraper.

**A run returned nothing — was I still charged?**

You aren't charged per *result* when there are none, but Apify **platform usage** (compute and residential proxy GB) is consumed by any run, including a blocked one. That's why validating with `maxItems: 5` is worth it.

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

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

***

### 🔗 Related Actors

#### Event data, by source

Pick by **where the events are listed** — or, if you need filtering rather than a single source, by the aggregator that carries District alongside five others.

| Actor | Source | Coverage | Why pick it over this one |
| :--- | :--- | :--- | :--- |
| **This Actor** | district.in (Zomato) | **India**, one city per run | Deepest pass over District's own feed — scrolls it to exhaustion, up to 200 events |
| [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) | **6 platforms incl. District** | Global | ✅ **Category, keyword and date-range filters** — the filtering this Actor has none of, plus five other sources in the same run |
| [All Events Scraper](https://apify.com/techforce.global/all-events-scraper) | allevents.in | **Global**, incl. India | Multi-city discovery, and **no residential-proxy requirement** — start here if District's setup is a blocker |
| [Eventbrite Scraper](https://apify.com/techforce.global/eventbrite-scraper) | eventbrite.com | Global | Organizer-led events, workshops, meetups and free events |
| [Eventim Scraper](https://apify.com/techforce.global/eventim-scraper) | eventim.de | Germany | Live **stock and price** signals for German ticketed events |
| [Events Eye Scraper](https://apify.com/techforce.global/events-eye-scraper) | eventseye.com | Global | Trade shows, B2B exhibitions and conferences |

> ⭐ **Need filters? Use [Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) instead.** It carries District as one of six sources and adds the three filters this Actor lacks — `category` (6 categories, or *any*), a free-text `search_query`, and a `date_from`/`date_to` range — with a per-source `limit` and one standardised output schema across all platforms. "Comedy in Bengaluru next weekend" is a single run there; here it's a full-feed scrape plus your own filtering.
>
> **Come back to this Actor when you want depth on District specifically** — it scrolls the city's feed to exhaustion for up to 200 events, where the aggregator caps each source at 100.

> ℹ️ **If you want Indian events without the residential-proxy dependency, start with [All Events Scraper](https://apify.com/techforce.global/all-events-scraper).** It covers Indian cities too, with a much lower setup bar. Note that Smart Event Scraper's District source hits the same IP fingerprinting this Actor does — so if the residential proxy is your blocker, the aggregator's other five sources are the way around it, not its District 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 |
| [Hospitality Lead Scraper](https://apify.com/techforce.global/hospitality-lead-scraper) | Bars, hotels and restaurants near a venue, for event-driven local outreach |
| [Advanced Website Crawling Actor](https://apify.com/techforce.global/advanced-website-crawling-actor) | Crawl a venue's or promoter's own site alongside the District listing |

#### Suggested pipeline patterns

**📅 City event feed**
Daily schedule per city → `deliveryMode: "chunked"` into Notion → a self-maintaining "what's on in Mumbai" page.

**🇮🇳 Multi-city India dataset**
Loop 5–8 cities, one run each → merge on `event_url` → a national view of what District is promoting, grouped by `city`.

**📊 Entry price-point research**
Same run across several cities → parse `price` → compare what comedy, music and experiences are priced at by market.

**🎯 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 ahead of the date.

**🌍 Complete city coverage**
[Smart Event Scraper](https://apify.com/techforce.global/smart-event-scraper) for filtered breadth across six platforms → this Actor for depth on District's own feed → dedupe on title + date + venue. Ticketed District listings *and* the grassroots long tail in one feed.

> 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 District, Zomato Limited, Eternal Limited, or any venue, artist or promoter named in its output. All trademarks are the property of their respective owners.

It reads **publicly available event listing pages** on district.in. It does not log in, does not accept credentials, and does not access anything behind authentication. It never books, reserves or buys tickets.

**The output is commercial event data, not personal data** — event titles, venues, dates, starting prices and public URLs. Performer names that appear inside `title` are stage and act names published commercially, so the privacy exposure here is minimal compared with a contact-data scraper. Three things still apply:

- **Prices are starting prices, captured at run time.** Republishing them as current — in an app, a listing site or a newsletter — commits you to a refresh cadence. Stale or misleading price displays can attract obligations under India's Consumer Protection Act 2019 and its e-commerce rules; always attribute the source and link through to `event_url` for the live price.
- **Respect the platform's own limits.** This Actor already blocks unnecessary resources, caps concurrency at 5 pages and backs off between retries. Don't defeat that by running many parallel runs against the same city — that's both an anti-abuse and a cost problem.
- **Residential proxy use carries its own terms.** You are responsible for using Apify Proxy within [Apify's terms](https://docs.apify.com/platform/proxy) and applicable law.

You remain responsible for ensuring your use complies with District's terms of service, applicable data-protection law (India's DPDP Act 2023, GDPR / UK GDPR / CCPA where relevant), and consumer-protection rules in the markets you operate in.

***

### 🆘 Support & Custom Pipeline Engineering

Need a multi-city India sweep on a schedule, change detection between runs, deeper per-event enrichment, or a full event-intelligence pipeline?

- **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

```json
{
  "@context": "https://schema.org/",
  "@type": "SoftwareApplication",
  "name": "District.in Event Scraper — Indian City Events, Venues & Ticket Prices",
  "applicationCategory": "BusinessApplication",
  "operatingSystem": "Cloud (Apify platform)",
  "description": "Scrapes live event listings from district.in (Zomato's events and ticketing platform) for one Indian city per run — event title, start date, venue, starting ticket price and the direct booking URL — by driving District's own location picker in a real browser over a residential Indian IP, reading each event page's embedded JSON-LD. Results are saved to a dataset and can be delivered into Notion, Slack, Airtable, Google Sheets or any authorized MCP connector. Requires Apify Proxy with the RESIDENTIAL group and country IN.",
  "url": "https://apify.com/techforce.global/district-event",
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "description": "Pay-per-result, charged per event returned. The current rate is listed on the Actor's Pricing tab."
  }
}
```

```json
{
    "@context": "https://schema.org/",
    "@type": "FAQPage",
    "mainEntity": [
                       {
                           "@type": "Question",
                           "name": "Why do I need a residential proxy? Can I skip it?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "No, not in practice. District fingerprints the caller's IP: from a datacenter range it serves the page shell but withholds the \"Select Location\" dialog, and the event feed never populates without a location. Residential access requires a paid Apify plan - that's the real entry requirement for this Actor."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Why does the proxy country have to be IN?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "district.in serves its event feed only to Indian IPs. From a residential IP elsewhere the page loads, the picker opens, and the feed comes back empty - indistinguishable from a quiet city unless you know to look. The Actor checks the actual egress country when a harvest is empty and tells you."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Do I need a District account or login?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "No - and none is accepted. Only public listing pages are read."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Is there a District API I could use instead?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "Not a public one. That's why this Actor drives a real browser and prefers each event page's embedded JSON-LD, which is the most stable structured data District exposes."
                                              }
                       },
                       {
                           "@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:"
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Which cities work?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "Indian cities District serves - Mumbai, Delhi, Bengaluru, Hyderabad, Pune, Chennai, Kolkata, Ahmedabad and others. location is free text, not an enum, so check district.in/events if a city returns nothing on a correctly configured proxy."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Can I scrape several cities in one run?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "No - one city per run. Loop from your orchestration layer; the Python example above shows the pattern."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Can I filter by category, date or price?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "Not at the source - those inputs don't exist here. Use Smart Event Scraper instead: it takes a category, a free-text search_query and a date_from/date_to range, and applies them across six platforms including District in one run. Otherwise, run this Actor unfiltered and filter the dataset - note that no Actor in the family filters on price, since District only publishes a starting price."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Does it get every event in the city?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "It scrolls the feed to exhaustion and takes up to maxItems (200 max). Whether that's \"every event\" depends on what District's feed surfaces for that city - treat it as the public feed, not a guaranteed complete inventory."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Why is venue sometimes null?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "Because District didn't expose one on that event page. It's the only genuinely nullable field."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "What does date: \"Open Dated\" mean?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "No start date was published - typical for long-running experiences and attractions rather than one-night shows."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "What does price: \"N.A.\" mean?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "No starting price was published. It does not mean free."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Is price the price I'll pay?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "It's the starting price for the event, as a display string with the currency symbol. Actual checkout depends on tier, seat and fees - present it as \"from X\"."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Why are the date formats inconsistent?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "JSON-LD gives ISO 8601; when JSON-LD is absent the selector fallback returns District's own display string. Parse ISO first and keep the raw text otherwise."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Can I get descriptions, images, lineups or seat availability?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "No - the output is six fields. Open event_url for anything more."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "How is this billed?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "Pay-per-result - charged per event returned, with no monthly subscription. The current rate is on the Actor's Pricing tab."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "Why is platform usage higher than for other scrapers?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "Because a real browser and residential proxy traffic are both required here. The Actor blocks images, fonts, media and trackers to cut it down, but it can't be as cheap as an HTTP-only scraper."
                                              }
                       },
                       {
                           "@type": "Question",
                           "name": "A run returned nothing - was I still charged?",
                           "acceptedAnswer": {
                                                  "@type": "Answer",
                                                  "text": "You aren't charged per result when there are none, but Apify platform usage (compute and residential proxy GB) is consumed by any run, including a blocked one. That's why validating with maxItems: 5 is worth it."
                                              }
                       },
                       {
                           "@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

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

City name as District lists it (e.g. Mumbai, Delhi, Bengaluru, Hyderabad, Pune, Chennai, Kolkata, Ahmedabad). District is an India-only platform - non-Indian cities return no events.

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

Maximum number of events to scrape (1-200). Free Apify plans are capped at 5 per run.

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

Proxy used to reach district.in. IMPORTANT: District blocks datacenter IPs — on the default proxy groups the location picker never loads and the run returns no events. Set the proxy group to RESIDENTIAL (and country to IN) for reliable results. Residential proxies require a paid Apify plan.

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

Optionally deliver 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 to the connector: 'perEvent' (one call per scraped event), 'summary' (one call with all events listed together), 'chunked' (split many events across a few calls/pages so services like Notion never time out), 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 'perEvent' mode: {title}, {date}, {venue}, {price}, {event\_url}, {city} and {message} (the rendered template below). In 'summary' mode: {city}, {eventCount}, {events} (all events formatted as text) and {message}. 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. Example for Slack: {"channel": "#events", "text": "{message}"}.

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

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

## Actor input object example

```json
{
  "location": "Mumbai",
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "IN"
  },
  "deliveryMode": "perEvent",
  "mcpTool": "",
  "mcpArguments": {},
  "mcpMessageTemplate": ""
}
```

# Actor output Schema

## `overview` (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/district-event").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/district-event").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/district-event --silent --output-dataset

```

## MCP server setup

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

```

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/ToxbCAhtq3iq9R95m/builds/gQfgm5yrlcYUWpWIc/openapi.json
