# Luma Events Scraper (lu.ma) (`xtracto/luma-events`) Actor

Scrape upcoming events from Luma by city: name, date and timezone, venue and coordinates, hosts, ticket price and availability, guest count and calendar. Keyless API, no login.

- **URL**: https://apify.com/xtracto/luma-events.md
- **Developed by:** [Farhan Febrian Nauval](https://apify.com/xtracto) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 91.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Luma Events Scraper — Upcoming Events by City from lu.ma

Scrape upcoming events from Luma, the platform most tech meetups, hackathons and community events
now run on: name, start and end times with timezone, venue and coordinates, hosts, ticket price and
availability, guest counts and the owning calendar. Keyless API, no login, no browser.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `places` | array | — | Luma city slugs as they appear at `lu.ma/<slug>` — `london`, `singapore`, `tokyo`, `berlin`, `jakarta` |
| `maxEventsPerPlace` | integer | `100` | 20 events per request |
| `includeGlobalDiscover` | boolean | `false` | Luma's worldwide featured feed — geo-personalised, so its size varies by where the run executes |

### Output

```jsonc
{
  "_input": "london",
  "_source": "S1-luma-api",
  "_scrapedAt": "2026-09-12T16:41:12Z",

  "eventId": "evt-bqICtbU4NTWPrxc",
  "name": "AI Healthcare Hackathon with Anima x OpenAI",
  "url": "https://lu.ma/animaxopenai", "slug": "animaxopenai",

  "startAt": "2026-09-12T07:30:00.000Z",
  "endAt": "2026-09-12T17:00:00.000Z",
  "timezone": "Europe/London",

  "locationType": "offline",
  "venueName": "Join Kopi Depok",          // null when the address is hidden
  "city": "London", "region": "England", "country": "GB",
  "fullAddress": "…",
  "addressIsHidden": false,
  "latitude": 51.5324597, "longitude": -0.1043,
  "searchedPlace": "London",

  "isFree": true, "price": null, "maxPrice": null,
  "isSoldOut": false, "spotsRemaining": 40,
  "isNearCapacity": false, "requiresApproval": true, "waitlistActive": false,

  "guestCount": 15, "ticketCount": 15,

  "calendarName": "Anima Events",
  "calendarId": "cal-LK7LUHWHXSJF4h5",
  "calendarAccessLevel": "public",

  "hosts": [{ "name": "…", "handle": "…", "bio": "…", "avatarUrl": "…" }],
  "hostCount": 4,

  "coverUrl": "…", "visibility": "public", "eventType": "independent"
}
```

### Three things worth knowing

**The same URL returns four different kinds of object.** `GET /url?url=<slug>` resolves a slug, and
its `kind` can be `discover-place` (a city), `calendar` (a calendar page, with no events array at
all), `event` (a single event — because some events own city-like slugs) or nothing. `new-york`
resolves to an **event**, not the city. `bandung` resolves to a **calendar**. Rather than return
silence, this actor checks `kind` and emits a `not_a_city` row naming what it actually got.

**The city parameter name matters, and the wrong one is ignored rather than rejected.** The correct
key is `discover_place_api_id`. Passing `place_api_id` — the obvious guess, and the name used inside
the place object — returns results **byte-identical to sending no parameter at all**: the global
feed, ~20 plausible-looking events with nothing to do with your city, and no error. Verified by
diffing the two responses.

**The global discover feed is geo-personalised, so its size depends on where the run executes.**
Paged to exhaustion it returned 21 events from an Indonesian IP and 60 from Apify's US datacenter —
same code, minutes apart. Treat it as a highlights reel, not coverage. Per-city paging is the
deterministic part: London reported `event_count: 52` and cursor paging returned exactly 52 unique
events. The run logs the city's own total before paging, so you can see whether you got all of it.

### A hidden address is a choice, not a gap

Organisers can hide the venue until you register, and Luma marks those. In one run, 33 of 79 events
had `addressIsHidden: true` — and those were exactly the 33 with no `venueName` and no
`fullAddress`, with the other 46 having both. `addressIsHidden` is there so an empty venue reads as
the organiser's decision rather than a scraping failure. **Coordinates are still present on hidden
events**, so mapping works either way.

### Errors

| `_error` | Meaning |
|---|---|
| `not_a_city` | The slug resolved to a calendar or a single event; `resolvedKind` says which |
| `no_events` | The city resolved but has nothing upcoming |
| `blocked` | The API refused, or a slug does not exist at all (404) |
| `network_error` | Never reached the server |

An event listed both in a city feed and in the global feed appears once per source, with different
`_input` — de-duplication is per source, because which feed surfaced it is usually the point.

If nothing is collected at all, the run fails rather than reporting success over an empty dataset.

# Actor input Schema

## `places` (type: `array`):

Luma city slugs as they appear at lu.ma/<slug> — london, singapore, tokyo, berlin, jakarta. Note that some events own city-like slugs: new-york resolves to an event, not the city, and the run says so rather than guessing.

## `maxEventsPerPlace` (type: `integer`):

Events come 20 per request. A city's true total is reported in the log before paging starts.

## `includeGlobalDiscover` (type: `boolean`):

Luma's worldwide featured feed. It is geo-personalised — paged to exhaustion it returned 21 events from one location and 60 from another — so treat it as a highlights reel rather than coverage of anywhere in particular.

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

Optional and not needed. Keyless public API with no anti-bot layer.

## Actor input object example

```json
{
  "places": [
    "london",
    "singapore"
  ],
  "maxEventsPerPlace": 100,
  "includeGlobalDiscover": false
}
```

# Actor output Schema

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

One row per scraped record, stored in the run's default dataset.

# 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 = {
    "places": [
        "london",
        "singapore"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("xtracto/luma-events").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 = { "places": [
        "london",
        "singapore",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("xtracto/luma-events").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 '{
  "places": [
    "london",
    "singapore"
  ]
}' |
apify call xtracto/luma-events --silent --output-dataset

```

## MCP server setup

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

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/OMJuFxhtnPdQxZtDw/builds/aothsUygseaY6Hhsh/openapi.json
