# Accupass Public Events (`stone_jadex/accupass-public-events`) Actor

Public Accupass events from the paginated listing API, with optional public event details.

- **URL**: https://apify.com/stone_jadex/accupass-public-events.md
- **Developed by:** [Stone Jade](https://apify.com/stone_jadex) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 results

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Accupass public events

Node.js 20 ESM Apify SDK v3 Actor. Uses HTTP only, with no browser, login or cookies. Lists public Taiwan Accupass events using `POST https://api.accupass.com/v3/search/SearchEvents`, relevance sort (`sortBy: "4"`), and zero-based pages of 25. Stops at the reported total or `maxItems`; duplicate event IDs are removed. Each page is written in one dataset batch.

Example input:

```json
{"keyword":"建築","maxItems":100,"fetchDetails":false,"includeAds":false,"includeOrganizerContact":false,"proxy":{"useApifyProxy":false}}
```

`keyword` defaults to empty; `maxItems` defaults to 1000 and must be a positive integer. `fetchDetails` defaults to false. `includeAds` defaults to false, excluding entries with `isAD: true` before detail requests and the item limit. Set it to true to include advertisements. Output URLs have `utm_*` tracking parameters removed; other query parameters and fragments are preserved. `includeOrganizerContact` defaults to false and only takes effect with details enabled. `proxy` accepts Apify proxy configuration or custom proxy URLs; direct connections are the default. The previous `proxyConfiguration` input is also accepted for compatibility.

Listing output includes event ID, title, public event URL, dates, timezone, city, event place type, tags, image, likes, page views, external link metadata and advertisement metadata. Views and likes are never reported as attendance; unavailable registration and attendee counts remain null. Detail-only fields remain null or empty in listing mode.

Details add plain-text description, venue/address, organizer name and public URL, category, and available ticket offers/price range. Offers depend on public structured data; missing offers remain empty, and a minimum registration-button price has an unknown maximum. These are not a guarantee of current availability or a complete ticket inventory. Only organizer email/phone explicitly published in organizer data are emitted when contact is enabled. No attendee personal data is extracted. Descriptions are public event content, not an email/contact discovery mechanism.

Dates in both modes use ISO 8601 with `+08:00` (Taiwan time), preserving the source instant. Categories come only from visible page category labels, with whitespace folded: for example `Food & Drink`, `Outdoor`, and `Science & Tech`. Internal category keys and the literal `None` are never used; missing visible labels produce `[]`. Visible online labels take precedence over structured location metadata, and online events always have `address: null` and `venue: null`. Offline venue text is copied from public structured data and can contain directions rather than a venue name. Listing images can be thumbnails; detail images use the public page image.

For output compatibility, unavailable fields remain explicit: `registrationCount` and `attendeeCount` are always null because this scraper has no public count source. `redirectUrl`, `linkUrl`, `advertIdNumber`, and `utm` are optional API metadata and are null when the API does not supply them; they are not inferred from other fields. `ticketPrices` is `[]` unless public Event JSON-LD supplies offers (none of the 30 independent QA pages did). A registration button supplies only `priceRange.min`, leaving `priceRange.max` null. These fields do not promise ticket inventory, attendance statistics, or a known maximum price.

The default key-value store receives `OUTPUT` and `RUN_SUMMARY` at the end, including failed scraping runs. Both contain `items` (successfully saved dataset records), `total` (the last API-reported search total, or null if unavailable), `mode` (`list` or `detail`), `truncated`, `failed`, and `detailFailures` (detail pages that could not be fetched). `OUTPUT` is the compact summary; `RUN_SUMMARY` also contains request, timing and memory counters plus `detailFallbacks` (pages saved with listing fields after missing Event JSON-LD). On successful runs, `truncated` marks stopping at `maxItems` with remaining eligible records or unread pages, or stopping 20 seconds before `ACTOR_TIMEOUT_AT`. A fully traversed search is not truncated merely because ads or duplicates were excluded; the API total is before those exclusions. On failed runs it is true when saved items are below a known total, or null when the total is unknown. Summaries describe saved batches, not a guarantee of completeness under changing search results.

The Actor checks `api.accupass.com/robots.txt` before listing and also checks `www.accupass.com/robots.txt` when details are requested. It honors disallow rules and crawl delays for its `AccupassPublicEventsActor` identity. Missing robots (404/410) allow access; other failures stop the run. Redirects are not followed. Detail requests run in waves of up to two; after any 429 subsequent waves use one. 429s have up to two retries with exponential backoff and Retry-After support. Listing and robots HTTP errors fail the run. A detail network or HTTP failure keeps that event's listing fields, sets `detailError` to the error message, and counts it in `detailFailures`; missing Event JSON-LD still uses the structure fallback. In-flight requests may finish after a 429. Failed runs can retain previously written page batches. Zero output fails the run, including a legitimate search with no matches.

Responses are capped at 5 MiB using got download progress cancellation, including decompressed bodies. Detail parsing reuses one page DOM and releases the HTML after parsing. Missing Event JSON-LD logs a warning and saves the listing item with null detail fields. Memory diagnostics log RSS, heap used, external memory and array buffers in bytes after Actor initialization, after input/proxy setup, after each list page, every ten detail attempts, and at the end. Each record includes a phase label.

The final summary logs request count (including robots and retries), saved items, 429 count, decoded response-body bytes (not billed proxy/wire bytes), mean detail operation duration including waits/retries, and elapsed seconds. `processRssSampledPeakBytes` replaces `rssMemoryPeak`: it records this Node process's RSS sampled every 100 ms and at request/completion boundaries during scraping, in bytes. `processRssLifetimePeakBytes` records the OS process RSS high-water mark (`process.resourceUsage().maxRSS * 1024`), including startup before scraping. Synchronous parsing can block timer samples, so use the lifetime value for the process peak. Neither metric is a platform/container memory measurement. A reported 175 MiB versus platform 125 MiB cannot be explained by MB/MiB conversion alone; the historical platform sampling/accounting data is needed to establish that difference. Memory defaults to 256 MB; the local 50-detail measurement below checks the 128 MiB target, while platform initialization, dataset writes and other page contents can change usage.

### Price

**What 1,000 events cost you: $1.50 in total.** Events are charged at $0.0015 per saved event ($1.50 per 1,000), with no run-start fee — you pay only for events actually saved to the dataset. There is currently no other Accupass scraper on the Apify Store, so this is the only priced option for Accupass event data.

#### Failed detail rows are still charged

Billing uses the pay-per-event `apify-default-dataset-item` event, which charges every row pushed to the dataset at the full per-event price. Rows with `detailError` (detail fetch failed) and rows with `detailSkipped: true` (reason `deadline`) are still written, so they are charged at the normal per-event price. `OUTPUT.detailFailures` and `OUTPUT.detailSkipped` count them.

Run locally with Node.js 20:

```sh
npm install
npm test
npm start
```

To measure 50 live details with a 100 MiB V8 heap and at most one request start per second (including robots and listing), run:

```sh
node --max-old-space-size=100 scripts/measure-memory.js
```

This uses the main scraper and Apify imports without platform initialization or dataset storage; it reports sampled RSS and the OS peak RSS.

Historical slicing experiment (subsequently reverted): on 2026-10-14, Node.js 20.19.2, two fresh processes ran that command (50 live details, 54 requests each, no 429s):

| Measurement | Before | After slicing |
| --- | ---: | ---: |
| OS lifetime peak RSS, bytes | 118,681,600 | 118,743,040 |
| OS lifetime peak RSS, MiB | 113.18 | 113.24 |
| 10 ms harness sampled peak RSS, bytes | 118,681,600 | 118,329,344 |
| Elapsed seconds | 54.771 | 61.673 |
| Response body bytes | 8,967,548 | 8,964,739 |

Both process peaks were below 128 MiB (134,217,728 bytes), and also below decimal 128 MB. This pair does **not** demonstrate an RSS reduction: the OS peak increased by 61,440 bytes (0.06 MiB). Live response contents and GC/allocator timing differ; tests ran in a separate process during the after measurement. The saved detail fixture shrank from 208,230 to 148,833 bytes before DOM construction (28.5%), with all output fields identical in fixture tests, including opt-in organizer contact. Raw logs: [before](docs/memory-before.log), [after](docs/memory-after.log). This local harness includes Apify imports but does not measure Actor initialization, platform memory accounting or dataset storage.

To isolate startup memory without live requests, run:

```sh
APIFY_LOCAL_STORAGE_DIR=/tmp/accupass-memory-diagnostic node --expose-gc scripts/diagnose-startup-memory.js
```

A local Node.js 20.19.2 run with Apify 3.7.2 measured the following RSS (MiB, rounded). Dynamic imports expose memory allocated before the normal startup log:

| Phase | RSS MiB |
| --- | ---: |
| Node baseline | 49.7 |
| After Apify import | 90.4 |
| After request module / got import | 102.5 |
| After hpagent import | 102.5 |
| After parser / Cheerio import | 108.9 |
| After scraper import | 109.2 |
| After Actor.init | 110.0 |
| After input / direct proxy setup | 110.9 |
| After custom proxy configuration | 111.2 |
| Saved list body buffered | 111.3 |
| Saved list body parsed | 111.4 |

Imports dominate early RSS in this measurement. Cheerio loads even for list-only runs because the parser imports it at module scope. The 16,089-byte saved list fixture added about 0.25 MiB across buffering and mapping; the scraper processes one list page at a time and clears response bodies after reading them. Larger live pages, concurrent detail bodies/DOMs, SDK dataset storage, and the growing deduplication set can add memory later. Releasing the fixture and forcing GC reduced heap used from about 31.0 to 20.6 MiB, while RSS stayed high (113.8 MiB): RSS includes runtime/native allocations and retained allocator pages, so heap release need not immediately reduce RSS. These sequential measurements include allocation/GC timing effects; they are indicative rather than isolated package benchmarks. Direct and custom proxy configuration were measured locally; managed Apify proxy initialization and live dataset writes were not measured.

Use Apify local input storage or the Apify platform input editor. The Dockerfile uses `apify/actor-node:20` and installs from the lockfile. Tests use saved fixtures and simulated HTTP responses; they do not measure live rate limits or guarantee future API/HTML compatibility. The listing endpoint and cost/rate observations come from `/workspace/tuning/2026-10-14-accupass-ratelimit.md`; site behavior may change. API and detail parse errors fail clearly rather than silently emitting empty output.

# Actor input Schema

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

Maximum unique events across API pages (25 per page).

## `fetchDetails` (type: `boolean`):

Fetch public event pages for description, address, organizer and available ticket prices.

## `includeOrganizerContact` (type: `boolean`):

Opt in to public organizer email/phone from event pages; requires fetchDetails. No attendee data.

## `keyword` (type: `string`):

Keyword sent to the public listing API.

## `proxy` (type: `object`):

No proxy by default.

## `includeAds` (type: `boolean`):

Include listing entries marked isAD true.

## Actor input object example

```json
{
  "maxItems": 1000,
  "fetchDetails": false,
  "includeOrganizerContact": false,
  "keyword": "",
  "proxy": {
    "useApifyProxy": false
  },
  "includeAds": false
}
```

# Actor output Schema

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

Scraped items in the 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 = {};

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

# Run the Actor and wait for it to finish
run = client.actor("stone_jadex/accupass-public-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 '{}' |
apify call stone_jadex/accupass-public-events --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stone_jadex/accupass-public-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/ScgGfB8rW9n0uxO3m/builds/JwdQpcJ0Vffaw3tnD/openapi.json
