# Apple App Store Developer Leads Scraper | Emails & Phones (`scrapersdelight/appstore-developer-leads-scraper`) Actor

$5 per 1,000. One row per App Store DEVELOPER, never per app: the publisher's EU trader contact block - legal entity, registered address, phone, email, D-U-N-S - plus website, support URL, portfolio size, ratings and tenure. Find them by keyword, category chart, developer ID or app ID.

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

## Pricing

from $3.00 / 1,000 per developer lead 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

## 🍎 Apple App Store Developer Leads Scraper — emails, phones & portfolios

**One row per App Store DEVELOPER, never per app.** Point it at a keyword, a category chart, a
list of developer IDs or a list of app IDs, and get back the *publisher* behind the apps: their
legal entity, registered address, phone number, email, D-U-N-S number, website, support URL —
plus how big their catalogue is, how much traction it has and how long they have been shipping.

The contact block is not scraped off somebody's homepage. It is **Apple's own EU Digital
Services Act Article 30 trader register**, which every trader distributing on an EU storefront
is legally required to declare and keep accurate.

```
Revolut Ltd        support@revolut.com   +44 2033228352   30 South Colonnade, London E14 5HX   DUNS 219783625
myCraftnote GmbH   info@mycraftnote.de   +49 1742058062   Heinrich-Heine-Platz 10, 10179 Berlin  DUNS 315098030
OnePageCRM         support@onepagecrm.com +1 6467621303   (Novus Via Ltd)                        DUNS 896941593
```

> ### ⚠️ Read this before your first run
>
> **The trader contact block exists only on EU storefronts.** Measured on one app id across four
> storefronts on 2026-09-07: populated on `ie` and `de`, **null on `us` and `gb`** — the UK is not
> in the EU. On a 25-developer `us` batch, `trader_*` filled **0 of 25**. That is why the
> storefront defaults to **`ie`** (the English-language EU storefront).
> If you need US developers, set `country: "us"` **and** put `["ie"]` in
> *Fallback storefronts* — measured below, that takes the trader block from **0% to 68%**.

***

### 📊 What you get on every row

| Group | Fields |
|---|---|
| 🏢 **Identity** | `developer_id` · `developer_name` · `developer_url` · `seller_name` · `copyright` |
| 📇 **EU trader register** | `is_trader` · `trader_name` (legal entity) · `trader_email` · `trader_phone` · `trader_address` · `trader_address_lines` · `trader_address_country` · `trader_duns` |
| ✉️ **Best contact** | `email` · `email_source` · `email_domain` · `email_is_role_address` · `email_is_freemail` · `description_emails` · `contact_channels` · `contact_source` · `contact_storefront` |
| 🌐 **Web** | `developer_website` · `support_url` · `privacy_policy_url` |
| 📈 **Portfolio economics** | `portfolio_app_count` · `portfolio_paid_app_count` · `portfolio_free_app_count` · `portfolio_total_ratings` · `portfolio_avg_rating` (ratings-weighted) · `portfolio_categories` · `first_release_date` · `newest_release_date` · `last_update_date` · `tenure_years` · optional `portfolio_apps[]` |
| 📱 **The lead app** | `lead_app_id` · `lead_app_name` · `lead_app_subtitle` · `lead_app_url` · `lead_app_bundle_id` · `lead_app_category` · `lead_app_price` · `lead_app_is_free` · `lead_app_rating` · `lead_app_rating_count` · `lead_app_version` · `lead_app_release_date` · `lead_app_last_updated` · `lead_app_has_in_app_purchases` · `lead_app_min_os` · `lead_app_languages` · `lead_app_icon_url` · optional `lead_app_description` |
| 🏆 **Chart position** | `lead_app_chart` · `lead_app_chart_genre` · `lead_app_chart_position` · `lead_app_chart_source` |
| ✅ **Integrity** | `portfolio_complete` + `portfolio_incomplete_reason` · `contact_complete` + `contact_incomplete_reason` · `contact_enriched` · `contact_used_alternate_app` · `contact_used_fallback_storefront` |
| 🧾 **Provenance** | `discovered_via` · `storefront` · `storefront_is_eu` · `scraped_at` |

***

### 📏 MEASURED field fill — real runs, not estimates

Every number below comes from a real run through the Apify datacenter proxy. Nothing here is
rounded up, and nothing is projected.

**These are one sample, not a guarantee.** Which 100 of the ~561 developers a keyword search
finds get delivered depends on the order Apple answers in, so an identical input run again moves
the softer fields by a few points. Table A was measured three times on the same input
(`crm`+`invoice`+`dental`+`plumbing`, `ie`, 100 developers) on 2026-09-07 and 2026-09-08; where
the three runs disagreed, the **range** is printed and the lowest number is the one to plan with.

#### A. 100 developers, `ie` storefront, keyword search (`crm`, `invoice`, `dental`, `plumbing`)

| Field | Filled | % |
|---|---:|---:|
| `developer_id` / `developer_name` / `developer_url` | 100/100 | **100%** |
| `support_url` | 100/100 | **100%** |
| `copyright` · `trader_name` | 100/100 | **100%** |
| `privacy_policy_url` | 99/100 | 99% |
| `portfolio_app_count` · `first_release_date` · `lead_app_*` | 100/100 | **100%** |
| `developer_website` | 66–68/100 | **66–68%** |
| `email` (any source) | 57–59/100 | **57–59%** |
| `is_trader = true` | 53–54/100 | **53–54%** |
| `trader_email` · `trader_phone` · `trader_address` | 53–54/100 | **53–54%** |
| `trader_duns` | 33–39/100 | **33–39%** *(the widest-swinging field of the set)* |
| `portfolio_avg_rating` | 36–50/100 | 36–50% *(null when the whole portfolio has zero ratings)* |
| `description_emails` (≥1) | 5–7/100 | 5–7% |
| `portfolio_complete = true` | 98/100 | 98% *(2 developers filled Apple's 200-app ceiling)* |
| `contact_complete = true` | 100/100 | **100%** |

`contact_source` across those 100 rows: **53–54 `apple-dsa-trader`**, 42–43 `app-metadata`,
3–5 `app-description`. Portfolio size: min 1, median 2–3, mean 10–25, max 106–201.

#### B. 50 developers, `ie` storefront, **top-grossing charts** (Business + Finance + Productivity)

The money charts are a much richer lead source, because a publisher earning revenue in the EU
almost always has to declare as a trader:

| Field | Filled | % |
|---|---:|---:|
| `support_url` · `privacy_policy_url` | 50/50 | **100%** |
| `email` (any source) | 44/50 | **88%** |
| `trader_email` · `trader_phone` · `trader_address` | 43/50 | **86%** |
| `trader_duns` | 37/50 | 74% |
| `developer_website` | 40/50 | 80% |

**If contact fill is what you care about, run the charts, not a keyword search.**

#### C. Non-EU storefront, with and without the EU fallback (25 developers, `us`, `crm` + `invoice`)

| | `trader_*` | any `email` | `developer_website` |
|---|---:|---:|---:|
| `country: "us"` alone | **0%** | 24% | 76% |
| `country: "us"` + `contactFallbackCountries: ["ie"]` | **68%** | 80% | 80% |

17 of the 25 trader blocks were recovered from the Irish storefront. Cost: one extra request per
developer that needed it, and **no extra charge**. This is the most-repeated measurement in this
README: three separate runs on three separate days each landed on **0/25 without the fallback and
17/25 with it**.

**EU → EU fallback adds nothing.** `ie` primary with `de` fallback measured **60% → 60%** over 25
developers. A developer's DSA declaration is consistent across EU storefronts, so only use the
fallback when your primary storefront is outside the EU.

#### D. Trying a second app for the trader block: measured zero

Over 100 developers on `ie`, `contactRetryApps: 2` spent **29 extra requests and recovered 0
extra trader blocks**. That is why the default is **1**. The knob is still there — the
measurement is one storefront on one day, not a law.

***

### ⚡ MEASURED speed and reliability

All through the **Apify datacenter proxy** (`BUYPROXIES94952`), concurrency 4, one pinned proxy
session per worker, on **2026-09-07**:

| Run | Developers | HTTP requests | Retries | HTTP 429 | Unrecoverable | Wall clock |
|---|---:|---:|---:|---:|---:|---:|
| Keyword search, `ie` (the demo input) | 10 | 25 | 2 | 1 | **0** | **15 s** |
| Keyword search `crm`, `ie` | 25 | 70 | 12 | 12 | **0** | 44 s |
| Top-grossing charts × 3, `ie` | 50 | 117 | 8 | 6 | **0** | 83 s |
| Keyword search × 4, `ie` | 100 | 266 | 33 | 32 | **0** | 131 s |

**The only wall Apple puts up is HTTP 429**, and it is per exit IP. There is no Cloudflare, no
CAPTCHA, no challenge page, no login and no token anywhere in this actor. Every 429 above was
recovered by retrying on a fresh proxy session. Running with **no** proxy from a single IP
measured **15 of 20** successes.

Cost per developer: **2 HTTP requests** (portfolio + contact), plus one shared discovery request
per keyword or chart.

***

### 🔎 Four ways to find developers — one row shape

| Mode | What it does | MEASURED supply |
|---|---|---|
| **Keyword search** | Runs App Store searches and collapses every result to its publisher | `crm`/`ie` → 179 apps → **162 developers**; `invoice`/`ie` → 186 → 169; `dental`/`ie` → 171 → 154; `plumbing`/`ie` → 113 → 88. Apple caps one search near 200 apps; the number of keywords is not capped |
| **Category charts** | Walks the top-free / top-paid / top-grossing chart of any of Apple's 26 categories | 100 entries per chart (Apple returns 100 even when more is asked for) → 89–100 distinct developers per chart. 26 categories × 3 charts ≈ **6,600 developers per storefront** |
| **Developer IDs** | You already know the publisher | Their whole storefront catalogue, up to Apple's 200-app ceiling |
| **App IDs** | You know the app, you want the company behind it | Looked up 50 ids per request |

**Start URLs work in every mode.** Paste `apps.apple.com/de/app/…/id123` or
`apps.apple.com/ie/developer/…/id456` and the storefront in the URL is honoured, so a German app
page is never looked up on the Irish storefront and reported as missing.

***

### 💰 Pricing — pay per event, nothing else

| Event | Price | When it fires |
|---|---:|---|
| **Per developer lead returned** (`developer-scraped`) | **$0.003** | Once per DEVELOPER row actually written to your dataset |
| **Per developer contact enriched** (`contact-enriched`) | **$0.002** | Once per delivered developer whose contact call returned at least one contact channel |

**A fully enriched lead ceilings at $0.005 — $5.00 per 1,000 verified developer leads.**
There is no actor-start charge and no per-dataset-item charge; both Apify auto-events are removed.

What you are **never** charged for:

- a developer removed by one of your filters (the filters run on the finished row — you keep the
  enrichment cost off your bill, not just the row)
- a developer skipped because `dedupeAcrossRuns` already delivered them
- a developer on your `excludeDeveloperIds` suppression list (they never cost a request either)
- a contact call that failed or came back empty
- anything at all when `fetchContacts` is off
- rows a charging cap truncated away — they are **not delivered and not billed**; the run says so
  in plain words and names the cap as the cause, both in the log and in the run's own **status
  message** (e.g. *"6 developer row(s) delivered | charged 6 developer-scraped + 6
  contact-enriched | STOPPED BY YOUR CHARGING LIMIT"*), so you never have to open the log to find
  out why a run is short

One developer is **one row and one charge** however many of their apps surfaced them. In the
100-developer run above, 645 app rows collapsed to 561 developers before a single charge.

***

### 🧾 Honest limits

1. **The trader block is EU-only.** Not a bug, not a fixable gap — Apple only publishes it where
   the DSA applies. `gb` is not in the EU. Non-EU storefronts still give you the developer, the
   portfolio economics, `support_url`, `privacy_policy_url` and `developer_website`.
2. **Fill is 54% (search) to 86% (top-grossing charts), not 100%.** The developers who come back
   without a contact block declared `isTrader: false` — governments, public bodies, some non-EU
   corporates and hobbyists. They still get a row, with `is_trader: false` and the reason visible.
   Turn on *Only developers with a trader contact* if you want the others gone (and unbilled).
3. **Apple caps the portfolio lookup at 200 apps per developer.** A developer who fills that
   ceiling gets `portfolio_complete: false` and a `portfolio_incomplete_reason` saying the totals
   cover 200 apps, not necessarily the whole catalogue. Measured: 2 of 100.
4. **Apple caps a chart at 100 entries** even when more is requested. This actor never claims 200.
5. **`portfolio_avg_rating` is null when the whole portfolio has zero ratings** (50 of 100 rows
   in run A). A null is a null — it is never reported as 0.0.
6. **Rate limiting is real.** At concurrency 4, 32 of 266 requests came back 429 on the largest
   run. All were recovered. Push concurrency past ~4 and the run gets slower, not faster.
7. **A row is a company, not a person.** `developer_name` is Apple's display name;
   `trader_name` is the legal entity, and they often differ (OnePageCRM → *Novus Via Ltd*).
8. **A short run timeout can cut discovery short, and the run says so out loud.** If the run's own
   clock runs out before every keyword / chart / storefront has been read, the log prints
   `INCOMPLETE DISCOVERY`, **names the searches that were never read**, sets
   `discoveryComplete: false` in `RUN_STATS`, and the run's status message carries it too. It is
   never reported as if Apple had nothing — and a request our own clock cut short is never blamed
   on Apple or on the proxy.
9. **`email_is_role_address` is a heuristic, not a verified fact.** It matches known role words in
   the local part (`support@`, `info@`, `hallo@`, and glued forms like `appservices@` or
   `dsalesmobilesupport@`). It cannot tell you that `eb@handwerkerpro.com` is a person and
   `crm@zohomobile.com` is a mailbox. Treat it as a sorting aid, and use `email` +
   `email_domain` when you need certainty.

***

### 🔐 Personal data — what this actor gives you control over

Apple's trader register is **business** contact data published under EU law. But a real share of
it is a named individual's work mailbox and mobile number, and a sole-trader developer's declared
address can be their home. Measured over a 100-row run on 2026-09-08: of the **57** rows carrying
an email, **28 were role addresses** (`support@`, `info@`, `hallo@`, `appservices@`) and **29
looked like a named person** (`matt.ackerman@csdental.com`, `tom.linehan@seapointclinic.ie`).
It is close to a 50/50 split, and the classifier is a word-match heuristic — see limit 9 above.

The actor ships that as-published, and gives you three switches plus two flags:

- **Drop named-individual email addresses** (`excludePersonalNameEmails`) — keeps only role
  mailboxes and reports how many were removed per row in `personal_emails_redacted`
- **Include the trader phone number** (`includeTraderPhone`) — off blanks it everywhere
- **Include the trader postal address** (`includeTraderAddress`) — off blanks it everywhere
- `email_is_role_address` and `email_is_freemail` on every row, if you would rather filter
  downstream than at scrape time

***

### 🎛️ Filters that pay for themselves

Every filter runs on the **finished** row, and a developer it removes is never delivered and
never charged:

`requireTraderContact` · `requireEmail` · `requireWebsite` · `excludeFreemailLeads` ·
`minPortfolioApps` / `maxPortfolioApps` · `minPortfolioRatings` · `minAverageRating` ·
`onlyPaidDevelopers` · `categoryIds` (applied *before* enrichment, so it costs nothing) ·
`newestReleaseAfter` · `firstReleaseAfter` / `firstReleaseBefore` · `excludeDeveloperIds`

`categoryIds` bites in **both** places: before enrichment when discovery already surfaced an app
(free), and again on the finished row against `portfolio_category_ids` — so it still filters when
you supplied bare developer IDs or `/developer/` start URLs and there was no discovered app to
match against. Either way a developer it removes is never delivered and never charged.

Example, measured: `requireTraderContact` + `minPortfolioApps: 2` + `minPortfolioRatings: 5` +
`excludeFreemailLeads` on `crm`/`ie` delivered **8 clean rows** and removed 20 candidates after
enrichment — all 20 unbilled.

***

### 🧑‍💼 Who buys this

- **Agencies and dev shops selling to app publishers** — a verified legal entity, a phone number
  and a portfolio size on one row is a qualified outbound list, not a scrape
- **SDK / API / analytics / monetisation vendors** — filter to `onlyPaidDevelopers` +
  `minPortfolioRatings` and you have publishers with a working paid product
- **M\&A and app-portfolio buyers** — `portfolio_app_count`, `portfolio_total_ratings`,
  `tenure_years` and `newest_release_date` are the first four columns of a target list
- **Compliance and brand-protection teams** — the DSA trader block is exactly the record you need
  to identify who is actually behind an app
- **Market researchers** — chart mode gives you the top 100 of any category with the publisher,
  their rank and their whole catalogue attached

***

### ❓ FAQ

**Does this return one row per app or per developer?**
Per **developer**, always. Apps are collapsed on Apple's `artistId` before anything is charged —
645 app rows became 561 developers in the measured run. The app the lead came through is kept on
the row as `lead_app_*`.

**Where does the email actually come from?**
`email_source` tells you per row. `apple-dsa-trader` means Apple's EU trader register — the
developer legally attested to it. `app-description` means it was in the App Store description
text. If neither exists, `email` is null; the actor never guesses an address.

**Why is `country` set to Ireland by default?**
Because it is the English-language EU storefront, and only EU storefronts carry the trader
contact block. Ireland gives you the register *and* readable English metadata.

**Can I get US developers with contact details?**
Yes — set `country: "us"` and `contactFallbackCountries: ["ie"]`. Measured: 0% → 68% trader fill.
Most sizeable US publishers also distribute in the EU and therefore declare there.

**Do I need a proxy?**
The Apify datacenter proxy is on by default and is enough — 100% success across every measured
run. Without a proxy, a single IP measured 15/20. Residential works but costs more for no gain.

**Does it need a login, an API key or a browser?**
No, none of the three. Four plain HTTP GETs that return JSON. That is also why it runs at 256 MB.

**How many developers can I actually get?**
Charts alone are roughly 26 categories × 3 charts × ~90 developers ≈ **6,600 per storefront**, and
there are 27 EU storefronts. Keyword search adds ~90–170 developers per keyword with keywords
uncapped.

**What happens if I hit my charging limit mid-run?**
The run stops, delivers only what it could pay for, and says *"the charging limit you set
(maxTotalChargeUsd, or your free-tier balance) left no room"* — naming the cap as the cause. You
are never left holding rows you were not billed for, or billed for rows you did not get.

**What does a zero-row run mean?**
It names the real reason: your filters removed everything, `dedupeAcrossRuns` had already
delivered them, the charging cap, this run's own time budget, or Apple genuinely returning
nothing for what you asked. It never blames Apple for something the actor did.

**Can I run it on a schedule and only get new developers?**
Yes. Turn on **Skip developers delivered by earlier runs**. Developer ids are remembered in a
named key-value store that survives between runs, so a scheduled run becomes a new-publishers
feed and you never pay twice for the same lead.

**How fresh is the data?**
Live. Every field is read from Apple at run time; nothing is cached between runs except the
dedupe list of ids you have already been sent.

**Can I export to CSV?**
Yes — the row is deliberately flat by default. `portfolio_apps` and `lead_app_description` are
the only heavy fields and both are opt-in.

***

### 🗂️ Output views

The dataset ships with three ready-made table views: **Developer leads** (the contact columns),
**Portfolio economics** (catalogue size, traction, tenure) and **Lead app** (the app and its chart
rank). A `RUN_STATS` record is written to the run's key-value store with request counts, retries,
429s, per-field fill over the delivered rows, what each filter removed, and why the run stopped.

***

### ⚖️ Legal & fair use

This actor reads **publicly available App Store data** — the same pages and endpoints Apple's own
web store front-end uses, with no login, no token, no credential and no access control bypassed.
The trader contact block is information Apple is required to publish under **Regulation (EU)
2022/2065 (Digital Services Act), Article 30**, precisely so that traders can be identified.

**robots.txt disclosure.** The endpoints this actor reads are Disallowed in Apple's robots.txt.
Quoted verbatim, read 2026-09-07:

```
## https://apps.apple.com/robots.txt
Disallow: /api/*
Disallow: */search?*

## https://itunes.apple.com/robots.txt
Disallow: /search*
Disallow: /*/rss/*
Disallow: /*/lookup?
```

robots.txt is a crawler-courtesy convention, not an access control and not a contract. We state it
plainly rather than hiding it; the data itself is public, unauthenticated and, in the case of the
trader register, published by legal mandate.

**Your obligations, not ours.** The trader block can contain a named individual's work email,
mobile number and — for sole traders — a home address. If you are in the EU/EEA or contacting
people there, GDPR applies to what *you* do with it: have a lawful basis (usually legitimate
interest for B2B outbound), honour objections and opt-outs, and identify yourself. Marketing rules
(ePrivacy/PECR and the equivalents) apply to how you use the emails and phone numbers. The
`excludePersonalNameEmails`, `includeTraderPhone` and `includeTraderAddress` switches exist so you
can narrow the data *before* it reaches your systems. Not legal advice — talk to your own counsel.

Apple, App Store, iTunes and the Apple logo are trademarks of Apple Inc. This actor is not
affiliated with, endorsed by, or sponsored by Apple Inc.

# Actor input Schema

## `country` (type: `string`):

The Apple storefront to scrape. THIS IS THE MOST IMPORTANT SETTING IN THE ACTOR. Apple publishes the developer's trader contact block - legal entity name, postal address, phone number, email, D-U-N-S number - only on EU storefronts, under EU Digital Services Act Article 30. MEASURED 2026-09-07 on one app id across four storefronts: the block is populated on 'ie' and 'de' and NULL on 'us' and 'gb' (the UK is not in the EU). Re-measured three times on 25-developer US batches: trader\_\* filled 0 of 25 every time. Default 'ie', the English-language EU storefront. On a non-EU storefront you still get the developer, the portfolio economics, support\_url, privacy\_policy\_url and developer\_website, but no email or phone from Apple.

## `discoveryMode` (type: `string`):

Keyword search = run App Store searches and take the publisher behind every app that comes back (MEASURED: 'crm' on ie returns 179 apps published by 162 distinct developers). Category charts = walk the top-free / top-paid / top-grossing chart of a category (Apple serves 100 entries per chart). Developer IDs / App IDs = you already know who you want. Start URLs below are honoured in EVERY mode, so you can mix a search with a hand-picked list.

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

One App Store search per keyword; the publishers behind every result are merged and de-duplicated on developer id. Used only in 'Keyword search' mode. Apple caps a single search at about 200 apps, so use several narrow terms rather than one broad one - the number of terms is not capped. MEASURED 2026-09-07 through the Apify proxy: 'crm' on ie = 179 apps / 162 developers, 'invoice' on ie = 186 / 169, 'crm' on us = 198 / 193. Leave this empty with no ids or URLs either and the run falls back to the documented sample search so it still returns rows instead of failing. If the run's own timeout expires before every keyword has been read, the log prints INCOMPLETE DISCOVERY and names the keywords that were never searched - it is never reported as though Apple had nothing.

## `searchLimitPerTerm` (type: `integer`):

How many apps to ask Apple for per keyword before collapsing them to developers. Apple's own ceiling is 200 and it often returns slightly fewer (179 for 'crm' on ie). Lower it to sample a term cheaply.

## `chartGenres` (type: `array`):

App Store categories whose charts to walk. Used only in 'Category charts' mode. MEASURED 2026-09-07: ie / Business / top-free returned 100 entries published by 92 distinct developers; ie / Finance / top-paid 97 entries / 76 developers. Multiply by the chart types below - 26 categories x 3 charts is roughly 6,600 developers per storefront.

## `chartTypes` (type: `array`):

Which chart of each category to read. Top-grossing is the money list: those publishers have a working paid product. MEASURED: Apple returns 100 entries per chart even when a higher limit is requested, so this actor never claims 200.

## `chartLimit` (type: `integer`):

How deep into each chart to read. Apple's hard ceiling is 100 entries per chart regardless of what is requested - asking for more does not fail, it just returns 100.

## `developerIds` (type: `array`):

Apple developer (artist) ids - the digits after /id in an apps.apple.com/<cc>/developer/... URL, e.g. 932493381 for Revolut Ltd. Used in 'Developer IDs' mode. Their whole storefront catalogue is read, so the portfolio lookup always runs for these even if 'Fetch the full portfolio' is off: it is the only way to learn anything about a developer given nothing but an id.

## `appIds` (type: `array`):

App Store app ids - the digits after /id in an apps.apple.com/<cc>/app/... URL, e.g. 932493382. Used in 'App IDs' mode. Each app is resolved to its publisher and you get the DEVELOPER row, not the app row. Ids are looked up 50 at a time. An id that is not on the chosen storefront is named in the log and skipped.

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

Paste apps.apple.com app pages or developer pages. Honoured in EVERY discovery mode, so you can bolt a hand-picked list onto a keyword search or a chart sweep. A /developer/...id<digits> URL adds that publisher; an /app/...id<digits> URL adds the publisher behind that app. Anything else is named in the log and skipped rather than silently dropped.

## `extraCountries` (type: `array`):

Run the same discovery on additional storefronts and merge the results. Developers are de-duplicated across storefronts on developer id - the first storefront that surfaced a developer is the one used for their portfolio and contact call, and the row's storefront field says which. Useful for an EU sweep (de, fr, it, es) in one run. Leave empty for a single-storefront run.

## `maxDevelopers` (type: `integer`):

Hard stop on DELIVERED rows, and therefore on your bill, since one row is one developer-scraped charge. It counts rows that survived your filters, not candidates discovered. 0 means no cap: everything discovery found is enriched and delivered. Start small.

## `fetchContacts` (type: `boolean`):

Makes the extra App Store call that returns the EU DSA trader block (legal name, address, phone, email, D-U-N-S) plus the support URL, developer website and privacy-policy URL. This is what the actor is FOR, so leave it on. Turning it off means no contact-enriched charge is ever made, and trader\_\*, support\_url and privacy\_policy\_url all come back null with contact\_complete = false and contact\_incomplete\_reason saying you switched it off.

## `fetchPortfolio` (type: `boolean`):

Reads the developer's whole catalogue on the storefront so portfolio\_app\_count, portfolio\_total\_ratings, portfolio\_avg\_rating, first\_release\_date and newest\_release\_date describe the DEVELOPER rather than the one app you found them through. Costs one extra request per developer and is not separately charged. Switch it off and those fields still ship, computed only over the apps discovery happened to surface, with portfolio\_complete = false and the reason spelled out on the row. Apple caps this lookup at 200 apps per developer; a developer who fills that ceiling also gets portfolio\_complete = false.

## `contactRetryApps` (type: `integer`):

A developer declares their trader details per app. If the first app carries no trader block, try this many of their apps in total (next-most-rated first) before giving up. MEASURED 2026-09-07 over 100 developers on the ie storefront: a second app cost 29 extra requests and recovered ZERO extra trader blocks - a developer's DSA declaration is consistent across their catalogue - so the default is 1. Raise it if you suspect a publisher declared on only some apps. Extra attempts cost requests, never an extra charge: a developer is billed once for enrichment however many apps it took.

## `contactFallbackCountries` (type: `array`):

When the chosen storefront returns no trader block for a developer, retry the contact call on these storefronts. Only EU storefronts publish the block, so listing 'us' or 'gb' here can never help and the log says so. The main use is a non-EU primary storefront: discover on 'us', then pull the EU-published trader contact from 'ie' or 'de'. Costs one extra request per fallback per developer that needed it, and still just one enrichment charge. The row's contact\_storefront field names the storefront the contact actually came from.

## `leadAppSelection` (type: `string`):

Every row carries one lead app - the app that puts the developer in context. 'The app that surfaced them' keeps the meaning of your search rank or chart rank. 'Most rated' picks their biggest app. 'Most recently updated' is the better activity signal. This also decides which app is tried FIRST for the trader block.

## `harvestDescriptionEmails` (type: `boolean`):

Many small developers put a contact email in the App Store description itself. This pulls them out of text you have already paid for - no extra request, no extra charge - into description\_emails, and uses the first one as the row's email when Apple published no trader email. It never invents an address; it only reports what is literally in the text.

## `excludePersonalNameEmails` (type: `boolean`):

Some developers register a named person's work mailbox as their trader contact (firstname.lastname@company.com) rather than a role address (support@, info@, hello@). With this on, only role addresses are kept and personal-looking ones are removed from trader\_email and description\_emails; the count removed is reported per row in personal\_emails\_redacted. Off by default - the actor delivers what Apple publishes - but this is the switch to use if your outbound programme only wants role mailboxes. Every row also carries email\_is\_role\_address so you can decide downstream instead. MEASURED over 100 delivered rows on 2026-09-08: of the 57 rows carrying an email, 28 were role addresses and 29 looked like a named person. The test is a word match on the local part - it catches support@, info@, hallo@ and glued forms like appservices@ or dsalesmobilesupport@, but it cannot tell you that eb@handwerkerpro.com is a person. It is a sorting aid, not a verified fact.

## `includeTraderPhone` (type: `boolean`):

Off blanks trader\_phone on every row. The number is published by Apple under DSA Article 30 and is often a mobile, so some buyers prefer not to store it at all.

## `includeTraderAddress` (type: `boolean`):

Off blanks trader\_address, trader\_address\_lines and trader\_address\_country on every row. Sole-trader developers frequently register a home address.

## `requireTraderContact` (type: `boolean`):

Keep only rows that carry a trader email, phone or postal address from Apple's EU register. MEASURED on ie: about 60-68% of developers qualify - the rest declared isTrader = false (governments, some non-EU corporates) and publish only a name. Applied to the finished row, so nothing it removes is delivered OR charged. On a non-EU storefront this filter removes everything, and the log says so.

## `requireEmail` (type: `boolean`):

Keep only rows whose email field is populated, from Apple's trader register or, failing that, from the app description. Applied after the personal-email switch above, so turning both on keeps only role addresses.

## `requireWebsite` (type: `boolean`):

Keep only rows with a developer\_website. That field's MEASURED fill is high but not total - about 88% on crm / ie and 56% on invoice / ie.

## `excludeFreemailLeads` (type: `boolean`):

Removes rows whose email is on a consumer mail host - a signal for a hobbyist rather than a company. Every row also carries email\_is\_freemail and email\_domain if you would rather sort it yourself.

## `minPortfolioApps` (type: `integer`):

Keep only developers publishing at least this many apps on the storefront. 2 or 3 filters out one-app hobby projects; a high number finds app studios and agencies.

## `maxPortfolioApps` (type: `integer`):

Keep only developers publishing at most this many apps. Use it to exclude the giants (Zoho publishes 163 apps on ie, Microsoft 78) when you are selling to small studios.

## `minPortfolioRatings` (type: `integer`):

Traction floor: the sum of every rating count across the developer's apps. Filters out developers whose apps nobody uses.

## `minAverageRating` (type: `integer`):

Keep only developers whose portfolio average rating - weighted by rating count, not a plain mean of stars - is at least this. 1 to 5.

## `onlyPaidDevelopers` (type: `boolean`):

Keep only developers with at least one paid app on the storefront - proof they already charge money for software.

## `categoryIds` (type: `array`):

Keep only developers with at least one app in these App Store categories. Applied BEFORE enrichment when discovery already surfaced an app, so a developer it removes costs you no requests at all - and applied AGAIN on the finished row against the developer's portfolio categories, so it still filters when you supplied bare developer IDs or /developer/ start URLs and there was no discovered app to match. Either way, a developer it removes is never delivered and never charged.

## `newestReleaseAfter` (type: `string`):

ISO date, e.g. 2024-01-01. Keep only developers who have shipped a NEW app since then - an activity signal that separates live studios from dormant accounts. Needs the portfolio lookup to be meaningful.

## `firstReleaseAfter` (type: `string`):

ISO date. Keep only developers who arrived on the App Store after this date - the young-company filter.

## `firstReleaseBefore` (type: `string`):

ISO date. Keep only developers who have been on the App Store since before this date - the established-company filter. Combine with the one above for a tenure window.

## `excludeDeveloperIds` (type: `array`):

Suppression list. Accepts developer ids or full apps.apple.com developer URLs. Applied before any request is spent on them, so existing customers and competitors cost you nothing.

## `includePortfolioApps` (type: `boolean`):

Adds portfolio\_apps - a nested array of the developer's apps (id, name, URL, category, price, rating, rating count, release and update dates), most-rated first. Off keeps the row flat and CSV-friendly; the portfolio\_\* totals are always present either way.

## `portfolioAppsCap` (type: `integer`):

How many apps the nested list may hold. The portfolio totals always cover every app that was read; portfolio\_apps\_listed and portfolio\_apps\_withheld on each row say how many of them made it into the array, so a capped list can never be mistaken for a complete one.

## `includeLeadAppDescription` (type: `boolean`):

Adds lead\_app\_description - the full App Store description, often a couple of thousand characters. Useful for classifying what the developer actually sells; heavy in a CSV.

## `dedupeAcrossRuns` (type: `boolean`):

Remembers every developer id delivered, in a NAMED key-value store that survives between runs, and skips them next time. Turns a scheduled run into a new-developers-only feed and stops you paying twice for the same lead. The skipped count is reported in the log.

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

Apify DATACENTER proxy is enough and is the default. MEASURED 2026-09-07 through groups-BUYPROXIES94952: 62 of 62 developers over 124 requests at concurrency 4, 100% success, zero blocks. The only wall Apple puts up here is HTTP 429, which is per exit IP and cleared by the built-in retry on a fresh session. RESIDENTIAL works too but costs more for no measured gain. Running with no proxy at all measured 15 of 20 successes from a single home IP.

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

How many developers to enrich at once, each on its own pinned proxy session. 4 is the measured sweet spot: 25 developers in 25.7 s with zero failures. Push it higher and Apple starts answering 429 - the retry handles it, but the run gets slower, not faster.

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

Attempts per HTTP request. The first rides the worker's pinned proxy session; every retry mints a FRESH session with exponential backoff, because a 429 belongs to the exit IP and a new IP starts on a full budget. MEASURED: 5 of 62 developers needed at least one retry and all 62 succeeded.

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

Per-request timeout. Apple's responses here run from 13 KB to 1.6 MB and normally answer in well under a second, so 30 s is generous. The timeout is also clamped to the time the run has left, so a retry chain can never outlive the run itself. If that clamp is what makes a request fail, the run says so in those words - it is never reported as an Apple or a proxy problem.

## Actor input object example

```json
{
  "country": "ie",
  "discoveryMode": "search",
  "searchTerms": [
    "crm",
    "invoicing",
    "field service"
  ],
  "searchLimitPerTerm": 200,
  "chartGenres": [
    "6000"
  ],
  "chartTypes": [
    "topfreeapplications"
  ],
  "chartLimit": 100,
  "developerIds": [
    "932493381",
    "388384807"
  ],
  "appIds": [
    "932493382",
    "444908810"
  ],
  "startUrls": [
    {
      "url": "https://apps.apple.com/ie/developer/revolut-ltd/id932493381"
    }
  ],
  "extraCountries": [],
  "maxDevelopers": 10,
  "fetchContacts": true,
  "fetchPortfolio": true,
  "contactRetryApps": 1,
  "contactFallbackCountries": [],
  "leadAppSelection": "firstDiscovered",
  "harvestDescriptionEmails": true,
  "excludePersonalNameEmails": false,
  "includeTraderPhone": true,
  "includeTraderAddress": true,
  "requireTraderContact": false,
  "requireEmail": false,
  "requireWebsite": false,
  "excludeFreemailLeads": false,
  "onlyPaidDevelopers": false,
  "categoryIds": [],
  "includePortfolioApps": false,
  "portfolioAppsCap": 25,
  "includeLeadAppDescription": false,
  "dedupeAcrossRuns": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 4,
  "maxRequestRetries": 4,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `items` (type: `string`):

One row per App Store DEVELOPER (publisher), de-duplicated on Apple's developer id: their EU DSA trader contact block, support URL, website, portfolio economics and the app the lead was found through.

## `runStats` (type: `string`):

RUN\_STATS — request counts, retries, HTTP 429s, per-field fill percentages over the delivered rows, what each filter removed, and why the run stopped.

# 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 = {
    "country": "ie",
    "discoveryMode": "search",
    "searchTerms": [
        "crm"
    ],
    "maxDevelopers": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/appstore-developer-leads-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 = {
    "country": "ie",
    "discoveryMode": "search",
    "searchTerms": ["crm"],
    "maxDevelopers": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/appstore-developer-leads-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 '{
  "country": "ie",
  "discoveryMode": "search",
  "searchTerms": [
    "crm"
  ],
  "maxDevelopers": 10
}' |
apify call scrapersdelight/appstore-developer-leads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/appstore-developer-leads-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/lRe5S55ZZzJ9pNfRM/builds/ypvJKgCIIRijw7vwD/openapi.json
