# MEDICA & Messe Düsseldorf Exhibitor Scraper (`scrapersdelight/medica-exhibitors-scraper`) Actor

From $1 per 1,000 exhibitors. Scrape the exhibitor directory of MEDICA, COMPAMED, K, ProWein, interpack, drupa, EuroShop, A+A, boot, glasstec and 7 more Messe Düsseldorf fairs. Filter by product category, hall, country, event or keyword. Optional phone, website, street address and named contacts.

- **URL**: https://apify.com/scrapersdelight/medica-exhibitors-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 per exhibitor returneds

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

## 🩺 MEDICA & Messe Düsseldorf Exhibitor Scraper — **filter by product category, not just "download the list"**

Messe Düsseldorf runs some of the largest B2B trade fairs on earth — **MEDICA**, **COMPAMED**,
**K**, **ProWein**, **interpack**, **drupa**, **EuroShop**, **A+A**, **boot**, **glasstec** and
more. Every one of them publishes a searchable exhibitor directory. This Actor turns any of them
into a clean company table: **name, country, city, postcode, hall and stand**, and — optionally —
**phone, website, full street address, profile description, product list and named contact people.**

**The part that makes it a targeting tool rather than a dump:** the fairs classify their own
exhibitors into a **product-category tree**, and this Actor lets you scrape a single branch of it.
A lab-diagnostics distributor does not want all 3,576 MEDICA exhibitors. They want the **515** in
*Laboratory, IVD & Diagnostics* — and the **64** of those that are German. Both numbers below are
real, from real runs, and both are one input field away.

***

### 📊 What you get, per row

Every row carries the same key set. A **listing-only run emits 27 columns**; switching on
**Fetch full detail adds 14**, and **contact persons add 2 more** (41 and 43 columns, verified on
runs `JGMPcspdZCLhHaq2L`, `x72sdbMHNw8KorvjT` and `BfilxVkncm1Ut2ZBl` — 1 key set in each).

#### The listing scrape — what every run returns

Fill measured across the **entire MEDICA + COMPAMED corpus, all 3,576 rows**, run
`ZSzg00nzcPc5MCt6e` (2026-09-08, build 0.1.2, complete sweep, 0 duplicates):

| Field | Fill | What it is |
|---|---|---|
| `exhibitorId` | **100%** | The fair's own id (`medica2026.3081101`) — the stable de-duplication key |
| `name` | **100%** | 🏢 Company name, exactly as the fair publishes it |
| `country` / `city` | **100%** | Where the company is based (not where its stand is) |
| `postalCode` | **99.8%** | Company postcode |
| `hallStand` | **100%** | 📍 `Hall 8b / J21` — the whole booth string |
| `hall` / `stand` / `halls[]` | **100%** | The same thing split, plus every extra booth a company holds |
| `eventId` | **100%** | Which fair inside a shared directory (`medica2026` vs `compamed2026`) |
| `eventName` | 100% on shared directories | The event's display label. Fairs whose directory holds only one event publish no per-row label, so this is `null` there — use `fairName` and `eventId` (see FAQ) |
| `fairDomain` / `fairName` | **100%** | Which fair site this row came from |
| `fairStartDate` / `fairEndDate` | **100%** | The edition's dates, read live from the fair's own config — so you always know *which* edition you scraped |
| `profileUrl` | **100%** | Deep link to the exhibitor's public profile page |
| `isPremiumExhibitor` | **100%** | Whether the fair flags them as a premium exhibitor |
| `teaser` | 16.6% | The short blurb shown on the search card (**92%** on K 2025 — see the maturity note) |
| `tags[]` | 14.9% | Self-declared criteria: Manufacturer · OEM · Service · Premium Exhibitor (MEDICA 2026, measured 2026-09-08: 477 / 193 / 191 / 5 — these move as exhibitors register) |
| `logoUrl` | 10.4% | Company logo / stand image |
| `queryLabel` / `queryDeclaredTotal` | **100%** | Which of your filters produced the row, and **how many the fair said that filter has** — so you can always see whether you got all of them |
| `categoryId` / `categoryLabel` / `categoryPath` | 100% *when you filter by category* | e.g. `medica2026.01.02` · `Laboratory, IVD & Diagnostics` · `MEDICA 2026 > Clinical Diagnostics & Therapeutic Technologies > Laboratory, IVD & Diagnostics` |
| `scrapedAt` | **100%** | ISO timestamp |

#### 🔎 With **Fetch full detail** switched on

One extra request per exhibitor. Measured on two real platform runs, because the numbers differ a
lot by **how close the fair is** (see the maturity note under it):

| Field | MEDICA 2026 (n=50, run `x72sdbMHNw8KorvjT`) | K 2025 (n=150, run `BfilxVkncm1Ut2ZBl`) | What it is |
|---|---|---|---|
| `phone` | **100%** | **99%** | ☎️ Company switchboard, international format |
| `website` | 94% | **99%** | 🌐 Company website — the field you pipe into an e-mail-finding tool |
| `street` | **100%** | **100%** | Street and number |
| `addressPostalCode` / `addressLocality` | 98% / **100%** | 99% / **100%** | Postcode and town from the detail record |
| `countryCode` | **100%** | **100%** | ISO-2 (`DE`, `IT`, `CN`) |
| `addressRegion` | 24% | 18% | State / region where the exhibitor gave one |
| `poBox` | 6% | 7% | Post-office box |
| `description` | 30% | **92%** | The company's own profile text, HTML stripped to clean text (or kept as HTML — your choice) |
| `products[]` | 16% | **86%** | 🧪 The product names the exhibitor lists on their own profile |
| `brands[]` | 4% | 3% | Registered brands |
| `socialProfiles[]` | 0% / 0% | 0% | Company-level social links (present in the schema, rarely filled by exhibitors) |
| `emailsInDescription[]` | 0% | 1% | ⚠️ **Not an e-mail field — read the honest-limits section.** These are addresses an exhibitor happened to type into their own description text |
| `detailFetched` | **100%** | **100%** | Whether the detail request actually succeeded for this row |

> **📅 The maturity effect — the single most important thing to understand about fill rates.**
> MEDICA 2026 opens on **2026-11-16**. Its exhibitors are still filling in their profiles, so
> descriptions sit at 30% today and will climb. A fair that has already happened is far richer:
> **K 2025 description 92%, products 86%.** The *address* block does not move — phone, street and
> town are ~100% on both, because the fair collects those at registration. If you need rich
> descriptions right now, scrape a completed fair (K, A+A, drupa, GMTN).

#### 👤 With **Include contact persons** switched on

The named staff an exhibitor chose to publish on its own profile — `name`, `givenName`,
`familyName`, `jobTitle`, `phone`, `linkedInUrl`, plus a `contactCount` per company.

| Fair | Exhibitors with ≥1 contact | People returned | Most on one company |
|---|---|---|---|
| K 2025 (n=150, run `BfilxVkncm1Ut2ZBl`) | **59%** | **197** | 9 |
| MEDICA 2026 (n=30) | 40% | 23 | 5 |
| ProWein 2026 (n=30) | 50% | 17 | 2 |
| A+A 2025 (n=30) | 40% | 26 | 6 |

Off by default, priced separately, and covered by its own paragraph in **Privacy** below.

***

### 🎪 Which fairs — 17 hostnames, 15 distinct directories, **28,698 exhibitors**

Every hostname below was probed live on **2026-09-07** and answered with a real exhibitor list;
the count is what the fair itself reported that day. Pick one from the dropdown, or type any other
Messe Düsseldorf hostname into **Custom fair hostname** — it is validated before anything is
scraped.

| Fair | Exhibitors | Notes |
|---|---|---|
| **MEDICA** — medical technology | 3,576 | shares its directory with COMPAMED (MEDICA 2,842 + COMPAMED 734) |
| **COMPAMED** — medical components | 3,576 | same directory; split with the **Events** filter |
| **K** — plastics & rubber | 3,237 | |
| **ProWein** — wine & spirits | 3,105 | adds a sales-channel filter |
| **interpack** — packaging & processing | 2,932 | shares with *components* (2,825 + 107) |
| **wire** — wire & cable | 2,656 | shares with Tube (1,510 + 1,146) |
| **Tube** — tube & pipe | 2,656 | same directory as wire |
| **A+A** — safety & health at work | 2,364 | completed fair, rich profiles |
| **GIFA / METEC / THERMPROCESS / NEWCAST** | 2,205 | four fairs in one directory (881 / 570 / 280 / 487) |
| **EuroShop** — retail | 1,900 | |
| **drupa** — print technology | 1,630 | 2024 edition, still served in full |
| **boot Düsseldorf** — water sports | 1,467 | |
| **glasstec** — glass | 1,173 | |
| **CARAVAN SALON** — caravanning | 873 | |
| **Valve World Expo** — valves | 549 | |
| **EuroCIS** — retail technology | 523 | |
| **BEAUTY DÜSSELDORF** — cosmetics | 508 | |

**One run covers one fair.** To sweep several, run the Actor once per fair (or schedule a task per
fair). Some Messe Düsseldorf brands are **not** on this platform at all — REIFEN, TOP HAIR,
Energy Storage Europe and a few others answer *"No config set found"*; the Actor stops with that
exact message and lists the ones that do work, instead of returning the wrong fair.

***

### 🎯 Who buys this

- **Distributors and importers** scouting manufacturers in one product niche — the category filter
  is the whole point: *Laboratory, IVD & Diagnostics* is 515 companies, not 3,576.
- **Contract manufacturers, packaging and sterile-barrier suppliers** selling *into* the exhibitor
  base of MEDICA/COMPAMED or interpack.
- **Trade-show and event agencies** building booth-services, logistics and stand-build prospect
  lists — filter by **hall** and you have the physical neighbourhood.
- **Market researchers** mapping who exhibits where, by country, over multiple editions (drupa 2024
  and GMTN 2023 are still served in full).
- **Sales teams working a fair**: sort by **Newest registered first** to see who signed up most
  recently, and by **hall and stand** to plan the walk.

***

### 🚀 Example inputs

**1 — a whole fair, cheapest possible (listing only)**

```json
{ "fair": "www.medica-tradefair.com", "maxItems": 0 }
```

3,576 rows, 37.7 s, `$3.58`. Verified: run `ZSzg00nzcPc5MCt6e`, 3,576 delivered / 3,576 charged.

**2 — one product category, with full contact detail**

```json
{
  "fair": "www.medica-tradefair.com",
  "productCategories": ["Laboratory, IVD & Diagnostics"],
  "includeDetail": true,
  "maxItems": 0
}
```

515 rows. The listing-only version of this run (`uHXQwOu4neWhyZiqB`) took 19.7 s and matched the
fair's own published count for that category **exactly: 515 declared, 515 delivered, 0 duplicates**.

**3 — German lab-diagnostics manufacturers only**

```json
{
  "fair": "www.medica-tradefair.com",
  "productCategories": ["Laboratory, IVD & Diagnostics"],
  "countries": ["DE"],
  "exhibitorCriteria": ["Manufacturer"],
  "includeDetail": true,
  "maxItems": 0
}
```

The category × country pair alone is **64 exhibitors** of 3,576.

**4 — everyone in one hall, for booth services**

```json
{ "fair": "www.medica-tradefair.com", "halls": ["Hall 1"], "sortBy": "location_asc", "maxItems": 0 }
```

**5 — keyword hunt across a fair**

```json
{ "fair": "www.medica-tradefair.com", "searchTerms": ["dialysis", "hemodialysis", "endoscope"], "maxItems": 0 }
```

Each keyword is its own query and results are de-duplicated: measured 8 + 1 + 4 = 13 hits,
**10 unique companies delivered, 3 duplicates suppressed and not charged.**

**6 — paste a URL from the fair's own website**

```json
{ "startUrls": [{ "url": "https://www.k-online.com/vis/v1/en/search?_query=extruder&f_country=DE" }] }
```

The host picks the fair, and the URL's filters and keyword are carried into the run.

**7 — one company, everything about it**

```json
{
  "startUrls": [{ "url": "https://www.medica-tradefair.com/vis/v1/en/exhprofiles/LBwn2IUJTPWeGs2rx27Jaw" }],
  "includeDetail": true,
  "includeContactPersons": true
}
```

**8 — the German edition of a fair**

```json
{ "fair": "www.k-online.com", "language": "de", "includeDetail": true, "maxItems": 0 }
```

Country names, hall labels, category names **and the profile description** all come back in
German — exhibitors often write the two language versions separately.

***

### 🔎 What you can filter on

All of it runs **server-side at the fair**, so you are only billed for rows you asked for.

| Filter | Accepts | Measured example (MEDICA 2026) |
|---|---|---|
| **Product categories** | the category **name** or its id | 68 categories over 3 levels; *Laboratory, IVD & Diagnostics* = 515 |
| **Countries** | ISO-2 or country name | Germany 644 · China 688 · France 144 (70 countries listed) |
| **Halls** | hall id or label | 18 hall areas; Hall 1 = 245 |
| **Events** | event name or id | MEDICA 2026 = 2,842 · COMPAMED 2026 = 734 |
| **Exhibitor criteria** | tag label or id | Manufacturer · OEM · Service (MEDICA 2026 counts move daily; 477 / 193 / 191 on 2026-09-08) |
| **Keywords** | free text | dialysis 8 · endoscope 15 · sterile packaging 16 · ultrasound 44 |
| **Other filters** | `f_targrp=…`, `f_industry=…`, `f_sales=…` | fair-specific: K and drupa have target groups, ProWein a sales channel, EuroShop its own tags |
| **Sort** | A-Z · Z-A · newest registered · oldest · by hall · relevance | changes *which* rows you get when `maxItems` cuts the sweep short |

> **Every filter value is checked against the fair's live filter list before the crawl starts.**
> Type `"Wakanda"` as a country and the run stops and prints the 70 countries that fair actually
> has — it does **not** quietly drop the filter and bill you for all 3,576 rows. That failure is a
> real, verified behaviour: run `KbqjOhhxEkjPqMaGl`, FAILED in 10 s, **$0.00 charged**.
> The full live vocabulary for the fair you picked is also written to the run's key-value store as
> **`FAIR_FACETS`**, so the next run can use exact ids.

***

### 💵 Pricing

Pay per event. **No monthly fee, no charge for starting a run, and no charge for a run that
returns nothing.**

| Event | Price | When it fires |
|---|---|---|
| **Per exhibitor returned** | **$0.001** | once per company written to your dataset |
| **Per detail fetch** | $0.002 | only with *Fetch full detail* on, and only for rows that were actually delivered |
| **Per contact person** | $0.001 | only with *Include contact persons* on, per named person returned |

| What you want | Cost |
|---|---|
| All 3,576 MEDICA + COMPAMED exhibitors, listing only | **$3.58** |
| The same, with phone / website / street address | **$10.73** |
| One product category (515 exhibitors) with full detail | **$1.55** |
| All 15 directories, 28,698 exhibitors, listing only | **$28.70** |
| A 50-row trial with full detail | **$0.15** |

**How this compares.** The established Messe Düsseldorf exhibitor scraper on this Store charges
**$0.005 per exhibitor** (free tier), $0.004 on paid tiers, **plus a $0.01 fee for starting each
run**, and runs at 1024 MB. Our listing row is **5× cheaper**, our full-detail row is **25%
cheaper**, there is **no start fee**, and this Actor runs at **256 MB** — measured peak memory use
was **64 MB** on the full 3,576-row sweep.

**Billing integrity, and how to check it yourself.** Every run above was verified with
`chargedEventCounts` **equal to rows delivered**, after waiting for Apify's counters to settle:

| Run | Rows delivered | `exhibitor-scraped` | `exhibitor-detail-enriched` | `contact-person-scraped` |
|---|---|---|---|---|
| `ZSzg00nzcPc5MCt6e` (full sweep) | 3,576 | 3,576 | 0 | 0 |
| `JGMPcspdZCLhHaq2L` (defaults) | 250 | 250 | 0 | 0 |
| `x72sdbMHNw8KorvjT` (50 + detail) | 50 | 50 | 50 | 0 |
| `BfilxVkncm1Ut2ZBl` (150 + detail + contacts) | 150 | 150 | 150 | 197 |
| `uHXQwOu4neWhyZiqB` (category, full) | 515 | 515 | 0 | 0 |
| `KbqjOhhxEkjPqMaGl` (bad filter) | 0 | **0** | 0 | 0 |

Duplicates are removed **before** billing, filtered-out rows are never charged, and if you set a
`maxTotalChargeUsd` cap the run trims the batch **before** it spends a detail request — so you
never receive enrichment your cap cannot pay for, and never pay for enrichment you did not receive.

***

### ⚙️ How it works, and what it does not hide

Messe Düsseldorf serves every fair's exhibitor finder from **one platform**, and the fair is
selected by a single request header. This Actor talks to that platform's own public JSON API
directly — **no browser, no login, no CAPTCHA, no cookies** — which is why it runs at 256 MB and
finishes a 3,576-company fair in under 40 seconds.

**Transport, measured through Apify's automatic (datacenter) proxy:**
`297 of 300` detail requests succeeded first try, the other 3 were connection resets recovered by
retry; **13 requests/second** at concurrency 10; median response **622 ms**, 95th percentile
**1.24 s**. Residential proxies also work and are **not** needed. The platform does rate-limit a
single address pushed hard, so HTTP 429 is retried with the server's own `Retry-After`.

**Three things this Actor refuses to paper over:**

1. **A filter the platform accepts but silently ignores.** The product-category tree exists in two
   different id-spaces, and passing the wrong one returns the *entire fair* with a 200 OK. Before
   delivering anything, the Actor compares the result count against the count the platform itself
   published for that exact filter. If a filter did not filter, the run **fails and delivers
   nothing** rather than billing you for thousands of rows you did not ask for.
2. **A truncated sweep is never reported as a complete one.** Every row carries
   `queryDeclaredTotal` — the fair's own count for the filter that produced it — and the run's
   status message names whatever cut the sweep short (`maxItems`, the run timeout, or your charge
   cap). The run's key-value store holds a **`RUN_SUMMARY`** record with a per-query
   `complete` flag and `cutShortBy` reason.
3. **A zero-row run says why.** "No exhibitor matched" is only ever printed when the platform was
   actually asked and answered. A run that ran out of time, hit a charge cap, or failed at the
   transport layer says exactly that instead, and a transport failure ends the run FAILED rather
   than pretending the fair is empty.

***

### 🚫 Honest limits — read before you buy

- **There is no e-mail address in this data.** The platform simply does not publish one. We checked
  hard: **300 exhibitor detail payloads across 4 different fairs were regex-scanned in full, and 4
  of them (1.3%) contained an e-mail-shaped string** — every one of those inside free text the
  exhibitor typed into their own description. Those are surfaced honestly in
  `emailsInDescription[]` and nowhere else. What you *do* get is **website at 94–99% and phone at
  99–100%**, which is what an e-mail-finding or contact-page tool needs as input. If a listing
  promises you exhibitor e-mails from this source, be sceptical.
- **Contact persons are not on every exhibitor** — 40% on MEDICA 2026, 59% on K 2025. And they are
  named individuals: see Privacy.
- **Description and product lists are thin on a fair that has not happened yet** (MEDICA 2026: 30%
  and 16% today) and rich on one that has (K 2025: 92% and 86%). This is a calendar fact about the
  source, not a limitation of the scraper.
- **`eventName` is `null` on single-fair directories.** The platform only publishes a per-row event
  label where a directory holds more than one fair. `fairName` and `eventId` are always filled.
- **One run, one fair.** There is no "all fairs" mode, because there is no route on the platform
  that lists the fairs — the 17 hostnames are hand-verified, and a new one can be typed into
  **Custom fair hostname** without waiting for an update.
- **Fairs are biennial or triennial**, so editions roll over (K 2025 → K 2027, drupa 2024 → 2028).
  Every row carries `fairStartDate`, `fairEndDate` and `eventId` read live from the fair's own
  config, so you always know exactly which edition you scraped.
- **Ratings, visitor numbers, stand size and floor-plan coordinates are not published** by this
  platform and are not returned.

***

### 🔒 Privacy — read this before switching contacts on

`includeContactPersons` returns **named natural persons** — first name, surname, job title,
business phone and LinkedIn URL — that exhibiting companies chose to publish on their own public
fair profiles. Most of them are in the EU, so **the GDPR applies to you as the controller of that
copy.** It is **off by default**, priced as its own event so that turning it on is a deliberate
act, and it is the only part of this Actor that touches personal data.

You need a lawful basis (in B2B prospecting this is usually legitimate interest), and you owe
those people the transparency, objection and erasure rights that come with it. Everything else the
Actor returns — company name, address, switchboard, stand, categories — is ordinary company data.
This paragraph is a description of the data, not legal advice.

***

### ⚖️ Legal and fair use

This Actor reads only **publicly available** exhibitor directory pages — the same pages any
visitor sees without logging in. No login, no paywall, no personal account, and no CAPTCHA is
circumvented.

`https://finder.messe-duesseldorf.de/robots.txt`, in full, on 2026-09-07:

```
User-agent: *
Disallow:  /kati-cgi/kati/
```

That is the entire file. The one disallowed path is a legacy CGI directory; **none of the routes
this Actor uses fall under it**. Each fair site's own `robots.txt` carries the same single
`Disallow` line, followed by its sitemap list — on `www.medica-tradefair.com` one of those
sitemaps is labelled **`Exhibitors & Products`**, i.e. the fair publishes these very pages for
search engines to crawl. Exhibitor profile pages additionally carry
`<meta name="robots" content="index,follow">`.

You are responsible for complying with Messe Düsseldorf's terms of use and with data-protection
law in your jurisdiction, especially if you enable contact persons. This Actor is not affiliated
with, endorsed by or connected to Messe Düsseldorf GmbH; MEDICA, COMPAMED, K, drupa, interpack,
ProWein, EuroShop, A+A, boot, glasstec, GIFA, METEC, NEWCAST, THERMPROCESS, EuroCIS, BEAUTY and
CARAVAN SALON are their trade marks, used here only to say which public directories are supported.

***

### ❓ FAQ

**Which fairs are supported?**
17 fair hostnames covering 15 distinct exhibitor directories and 28,698 exhibitors — the table
above lists every one with its live count. Any other Messe Düsseldorf hostname can be typed into
**Custom fair hostname**; it is validated against the platform first.

**Can I scrape several fairs in one run?**
No — one run covers one fair, which keeps billing and de-duplication honest. Create one Apify task
per fair and schedule them; each takes well under a minute for a listing-only sweep.

**How do I find the exact product-category name?**
Type the name as you see it on the fair's own site — matching is case-insensitive and accepts a
distinctive fragment. Or run once with no category and read **`FAIR_FACETS`** from the run's
key-value store: it lists every filter id, label and live count that fair accepts, including the
top of the category tree. Category ids like `medica2026.01.02` also work directly.

**Why did my run stop with "filter value could not be applied"?**
Because applying no filter would have returned — and billed you for — the whole fair. The error
lists the values that fair really has. Copy one and re-run; nothing was charged.

**Does it get e-mail addresses?**
No, and nothing on this page implies otherwise. The platform publishes website and phone, not
e-mail. See Honest limits for the 300-profile measurement behind that statement.

**How fresh is the data?**
It is read live at run time; there is no cache. Exhibitor lists for an upcoming fair change weekly
as companies register — sort by **Newest registered first** to see the new arrivals.

**Can I get exhibitors from a fair that already happened?**
Yes. drupa 2024 still serves 1,630 exhibitors and the GMTN 2023 group serves 2,205. Completed
fairs also have the richest profiles.

**What is the `queryDeclaredTotal` column for?**
It is the fair's own count for the filter that produced that row. Compare it with how many rows you
got: equal means you have the complete set, fewer means something truncated the sweep — and the run
status message and `RUN_SUMMARY` name what.

**Will a run ever bill me for rows I did not want?**
The design says no, in four places: filters are validated against the live vocabulary before the
crawl; a filter that does not actually filter fails the run; duplicates are removed before billing;
and the second-order events (detail, contacts) are charged only for rows that were actually
delivered.

**What if I set a maximum charge for the run?**
The run reserves money, not row counts. It trims the batch **before** spending detail requests,
tells you in the log and status message that the cap was what stopped it, and never delivers a row
it could not bill for.

**Why 256 MB when other exhibitor scrapers use 1024 MB?**
Because this one uses no browser. Peak memory on the full 3,576-row sweep was 64 MB. You pay less
platform compute for the same rows.

**Can I get the raw data behind a field you did not map?**
Yes — switch on **Include the raw source payload** and each row carries the untouched listing
document and the exhibitor's schema.org JSON-LD.

**Does it work with the Apify API, schedules and integrations?**
Yes — it is a normal Actor. Results go to a dataset you can pull as JSON, CSV, Excel or XML, and
the dataset ships with four ready-made views (Exhibitors, Company leads, Contact persons, By
product category).

# Actor input Schema

## `fair` (type: `string`):

Which Messe Düsseldorf exhibitor directory to scrape. Each entry is a fair hostname that was probed live and answered with a real exhibitor list. MEDICA and COMPAMED share one directory (as do wire/Tube, interpack/components and the four GMTN fairs) — pick either and use 'Events' to split them.

## `customFairDomain` (type: `string`):

The hostname of a Messe Düsseldorf fair that is not in the list above, e.g. a new edition launched after this Actor was published. Type the bare host, such as www.medica-tradefair.com. It is validated against the platform before anything is scraped; a hostname the platform does not know stops the run with its own error rather than returning the wrong fair. Leave empty to use the picker.

## `startUrls` (type: `array`):

Paste URLs from a fair's own exhibitor finder. A search URL carries its filters and keyword into this run (…/search?f\_country=DE&\_query=endoscope). An exhibitor profile URL (…/exhprofiles/<id>) scrapes just that company. A directory letter URL is treated as the whole directory. The URL's host also selects the fair, overriding the picker above.

## `searchTerms` (type: `array`):

Full-text search over exhibitor profiles and their products — measured on MEDICA 2026: "dialysis" 8 exhibitors, "endoscope" 15, "sterile packaging" 16, "ultrasound" 44. Each keyword is crawled as its own query and results are de-duplicated, so overlapping keywords are never billed twice.

## `productCategories` (type: `array`):

The fair's own product-category tree — the filter a distributor actually wants. Give the category NAME ("Laboratory, IVD & Diagnostics") or its id ("medica2026.01.02"); names are matched against the live tree, shallowest match first. MEDICA 2026 publishes 68 categories over 3 levels, e.g. Laboratory, IVD & Diagnostics = 515 exhibitors of 3,576. Names are resolved before the crawl and an unmatched name stops the run — it never falls back to the whole fair. Every fair has its own tree; the live list for the fair you picked is written to this run's key-value store as FAIR\_FACETS.

## `countries` (type: `array`):

ISO-2 code ("DE", "CN") or country name ("Germany"). Several values are OR-ed together. Measured on MEDICA 2026: Germany 644, China 688, France 144.

## `halls` (type: `array`):

Hall id ("01", "08B") or hall label ("Hall 1"). Several values are OR-ed. Useful for planning a walk of the fair or for targeting a country pavilion. MEDICA 2026 has 18 hall areas; Hall 1 holds 245 exhibitors.

## `eventIds` (type: `array`):

Some directories carry two or more fairs at once. Use this to split them: on the MEDICA/COMPAMED directory "MEDICA 2026" is 2,842 exhibitors and "COMPAMED 2026" is 734. Give the event name or its id (medica2026, compamed2026, wire2026, tube2026, interpack2026, components2026, GMTN2023.gifa …). Leave empty to get every event in the directory.

## `exhibitorCriteria` (type: `array`):

The self-declared company-type tags a fair offers, e.g. Manufacturer, OEM, Service on MEDICA (477 / 193 / 191 exhibitors on 2026-09-08; the counts move as exhibitors register). Give the label or the tag id. The tag vocabulary differs per fair — see FAIR\_FACETS in this run's key-value store.

## `extraFilters` (type: `array`):

Filters only some fairs publish, written as facet=value. ProWein adds f\_sales (sales channel) and f\_industry; K and drupa add f\_targrp (target group); EuroShop adds f\_itag. Values are validated against the fair's live facet list where it exposes one; where it does not, the filter is passed through and the run log says so rather than pretending it was checked.

## `sortBy` (type: `string`):

Server-side ordering. A-Z is the stable default and is what a directory export should look like. 'Newest registered first' surfaces the companies that signed up most recently — the trigger a sales team wants. Note the sort decides WHICH rows you get when 'Maximum exhibitors' cuts the sweep short.

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

Hard cap on rows delivered and billed across the whole run. Set 0 for no limit — the biggest single directory held 3,576 exhibitors on 2026-09-07, and all 15 directories together held 28,698. The default of 250 exists so an accidental run cannot bill a full sweep; raise it deliberately.

## `startOffset` (type: `integer`):

Start the sweep at this offset instead of the beginning — use it to resume a big export in slices without re-billing the rows you already have. Keep the sort the same between slices or the slices will overlap.

## `includeDetail` (type: `boolean`):

One extra request per exhibitor for the company's phone number, website, full street address, profile description, product list and brands. Measured fill on MEDICA 2026 (n=30): phone 100%, street 100%, city 100%, postcode 97%, website 90%, description 40%. On fairs that have already taken place the descriptions are far fuller (K 2025: description 90%, products 87%). Billed as a separate event, so a listing-only run never pays for it.

## `includeContactPersons` (type: `boolean`):

Adds the named staff an exhibitor chose to publish on its own profile: name, job title, phone and LinkedIn URL. Measured coverage: MEDICA 2026 40% of exhibitors (23 people across 30), K 2025 63% (47 people across 30). These are identifiable natural persons, mostly in the EU — you are responsible for having a lawful basis to process them. Off by default and billed per person. Needs 'Fetch full detail', which is switched on automatically if you enable this.

## `descriptionFormat` (type: `string`):

The profile description is stored as HTML. 'Plain text' converts it for spreadsheets and LLM pipelines; 'HTML' keeps the original markup; 'Both' emits both columns. Only applies when 'Fetch full detail' is on.

## `includeRawPayload` (type: `boolean`):

Adds the untouched listing document and the exhibitor's schema.org JSON-LD to each row, for anyone who wants a field this Actor does not map.

## `deduplicateById` (type: `boolean`):

Keeps one row per company across overlapping keywords, categories and filters. De-duplication runs BEFORE billing, so a company matched by three of your keywords is delivered once and charged once. Turn it off only if you want to see every match separately.

## `language` (type: `string`):

Which language the fair should answer in. It changes country names, hall labels, category names and the profile description — the German edition of a profile is often written by the exhibitor separately from the English one.

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

Apify's automatic (datacenter) proxy is enough: 297 of 300 detail calls succeeded first try at 13 requests/second, with the 3 misses recovered by retry. Residential also works and is not needed. You may plug in your own proxies here.

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

Parallel requests. 10 measured 13 requests/second with no rate-limiting. The platform does rate-limit a single address pushed hard; if you supply your own single-IP proxy, lower this.

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

Per-request timeout. Median response measured at 622 ms, 95th percentile 1.24 s.

## `maxRequestRetries` (type: `integer`):

Retries for connection resets, HTTP 429 (the Retry-After header is honoured) and 5xx. A 404 or other 4xx is a real answer and is not retried.

## Actor input object example

```json
{
  "fair": "www.medica-tradefair.com",
  "startUrls": [],
  "searchTerms": [],
  "productCategories": [],
  "countries": [],
  "halls": [],
  "eventIds": [],
  "exhibitorCriteria": [],
  "extraFilters": [],
  "sortBy": "alpha_asc",
  "maxItems": 50,
  "startOffset": 0,
  "includeDetail": true,
  "includeContactPersons": false,
  "descriptionFormat": "text",
  "includeRawPayload": false,
  "deduplicateById": true,
  "language": "en",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 10,
  "requestTimeoutSecs": 45,
  "maxRequestRetries": 3
}
```

# Actor output Schema

## `exhibitors` (type: `string`):

Company name, country, city, postcode, hall and stand, fair and event, self-declared criteria and profile URL — plus phone, website, full street address, description, products and named contact persons when the optional detail and contact-person fetches are switched on.

# 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 = {
    "fair": "www.medica-tradefair.com",
    "startUrls": [],
    "sortBy": "alpha_asc",
    "maxItems": 50,
    "includeDetail": true,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/medica-exhibitors-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "fair": "www.medica-tradefair.com",
    "startUrls": [],
    "sortBy": "alpha_asc",
    "maxItems": 50,
    "includeDetail": True,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/medica-exhibitors-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "fair": "www.medica-tradefair.com",
  "startUrls": [],
  "sortBy": "alpha_asc",
  "maxItems": 50,
  "includeDetail": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/medica-exhibitors-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/medica-exhibitors-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/F7oqJ1KUV44awKnDV/builds/1kPkfA4VvLVvdOaFi/openapi.json
