# Apple Maps Scraper - Place Details, Hours & Change Monitor (`neverempty/apple-maps-place-scraper`) Actor

For local SEO and listings teams watching their places: Apple Maps details by place URL or ID with name, address, phone, website, opening hours, rating and count, categories and closed status. About 1 s per place. Monitor returns only places whose hours, phone, rating or status changed.

- **URL**: https://apify.com/neverempty/apple-maps-place-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, Automation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.60 / 1,000 place returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Apple Maps Scraper - Place Details, Hours & Change Monitor

Get the details of any **Apple Maps** place from its place URL or place ID: **name, address, phone, website, opening hours, rating and number of ratings, categories, coordinates** and whether it is **temporarily or permanently closed**. Turn on **monitor mode** and each run returns **only the places whose hours, phone, website, address, rating, rating count, category or open/closed status changed** since the last run, with the previous values.

Built for lookups one place at a time as well as for lists: the **run start fee is low**, so an API call for a single place stays cheap, and in monitor mode an unchanged place costs only a small check fee.

Unofficial. Reads only the public Apple Maps place pages (`maps.apple.com/place?...`) that Apple's robots.txt explicitly allows. No login, no Apple account, no search pages.

### What you get

| Column | Example |
|---|---|
| `name` | Black Fox |
| `primaryCategory`, `categories` | Coffee Shop; Coffee Shop, Cafe, Restaurant, Breakfast and Brunch Restaurant |
| `phone`, `phoneFormatted`, `altPhones` | +19177420133 ; +1 (917) 742-0133 ; \[+12124227750] |
| `website` | https://www.blackfoxcoffee.com |
| `address` + `street`, `city`, `district`, `county`, `state`, `stateCode`, `postalCode`, `country`, `countryCode` | 70 Pine Street, New York, NY 10005, United States |
| `latitude`, `longitude`, `timezone` | 40.7064705, -74.0077107, America/New\_York |
| `openingHours` | `{ "monday": ["07:30-17:00"], ..., "sunday": ["09:00-16:00"] }` (local time; a day with `[]` is closed) |
| `businessStatus` | `operating`, `temporarily-closed` or `permanently-closed` (plus `isTemporarilyClosed`, `isPermanentlyClosed`, `hoursType`) |
| `rating`, `ratingMaxScore`, `ratingOutOf5`, `ratingCount`, `ratingProvider` | 3.9 of 5 from 334 ratings (Yelp) - or 85 of 100 from 54 ratings (Apple) |
| `ratingCategories` | Apple's per-topic ratings when shown (Atmosphere 86%, Customer Service 85%, ...) |
| `priceLevel`, `priceLevelMax` | 2 of 4 |
| `amenities` | ACCEPTS\_APPLE\_PAY, WHEELCHAIR\_ACCESSIBLE, ... |
| `placeId`, `auid`, `placeUrl`, `mapsCategoryId`, `placeType`, `claimable` | I918CB7E77B4C7910 ... |
| `changeType`, `changedFields`, `previousValues`, `previousCheckedAt` | with a watch: `changed`, `["phone"]`, `{ "phone": "+19177420133" }` |
| `partsNotRead` | parts of the page Apple did not deliver this time (for example `["rating","ratingCount"]`) - those columns are `null` and are not compared |

Apple Maps shows ratings in two ways and this Actor keeps both as shown: **Apple's own ratings** (a percentage, `ratingMaxScore` 100) and **ratings from a partner such as Yelp** (stars, `ratingMaxScore` 5). `ratingOutOf5` puts both on a 5-point scale.

### Input

| Field | What it does |
|---|---|
| `places` | Apple Maps place URLs or IDs, one per line. Accepted: `https://maps.apple.com/place?place-id=I918CB7E77B4C7910`, `https://maps.apple.com/place?auid=5283835567047557515`, older links like `https://maps.apple.com/?auid=...&q=...`, or just the place ID / auid. Up to 1,000 per run. Empty = three example places. Short share links (`maps.apple/p/...`) are not read - open them and copy the full address. |
| `onlyChanges` | Monitor mode. Return only places that changed (or are new to the watch). The first run returns every place as the starting point. |
| `watchName` | Name of the remembered state, so different lists can be watched on their own schedules. |
| `resetMonitoringState` | Forget what the watch remembered and start over. |

Example - one place (the typical API call):

```json
{ "places": ["https://maps.apple.com/place?place-id=I918CB7E77B4C7910"] }
```

Example - a daily change monitor for your locations:

```json
{ "places": ["I918CB7E77B4C7910", "I9B7EE73B37C8175F", "5283835567047557515"], "onlyChanges": true, "watchName": "my-stores" }
```

### Monitor mode: what counts as a change

Compared fields: `name`, `phone`, `website`, `address`, `openingHours`, `businessStatus`, `rating`, `ratingCount`, `primaryCategory`.

Not compared, because they move on their own and would be sold as fake changes every day: the "open now / closed now" state, the rolling 7-day window Apple attaches to a temporary closure, photo order and review snippets.

Every change is **confirmed by reading the page a second time**; only fields that show the same new value in both reads are reported. If the second read fails, the change is not reported, charged or remembered, and a free `unreadable` row says so; the next run checks the place again. We measured why this matters: the page of one permanently closed store came back without its rating block in 7 of 10 reads (the other 26 places we re-read 5 times each never changed).

- A rating or rating count that was there before and is missing today is treated as **not delivered today**, not as removed: it is not reported and the remembered value is kept (`partsNotRead` says so on the row).
- A rating that was missing when the place was first remembered and shows up later is remembered silently, not sold as a change. This also means a place that receives its very first rating is not reported until that rating changes again.
- If Apple marks a part of the page as not delivered (each part of the page carries its own status), that part is not compared and the remembered value is kept.

### How it behaves when something goes wrong

- **The place does not exist** (Apple answers 404): a free `not-found` row. Not retried.
- **The page could not be read** after asking again (twice directly, twice through a datacenter IP, then a residential IP - at most 5 residential reads per run): a free `unreadable` row. It is never reported as "no data". The place is not remembered, so a later run returns it.
- **A check page / CAPTCHA**: this Actor does not solve or bypass it. It stops, writes a free `blocked` row and charges nothing.
- **Apple redirects a place somewhere this Actor does not read**: a free `redirected` row that says where it was sent.
- **The rating block is missing**: for a few places Apple sometimes sends the page without its rating (we saw it on one permanently closed store). A single lookup cannot tell that apart from a place with no ratings, so `rating` is `null` on that row; monitor mode keeps the remembered rating (see above).
- **Your maximum charge per run is reached**: it stops before reading places it could not bill, says so in a free `budget-reached` row and does not remember them.
- **Bad input**: a free `bad-input` row, nothing is requested.
- A run that could not read anything and charged nothing ends with a failed status, so your integration sees it.

### Pricing

Pay per event:

- **Run start** - once per run that returns at least one place (in monitor mode: once per run that read and compared at least one place).
- **Place returned** - per place row.
- **Place checked** - monitor mode only, per place that was read and had no change (the place is not returned as a row).

Rows that explain a missing place, an unreadable page, bad input, no change or a reached limit are free. The exact prices are on the Pricing tab.

### Notes and limits

- Data is what Apple Maps shows on the public place page, in English (en-US).
- Opening hours are the regular weekly hours in the place's local time zone, split at midnight the way Apple Maps stores them: a club open Friday 11 PM to 4 AM shows `"friday": ["23:00-24:00"]` and `"saturday": ["00:00-04:00", ...]`. Places without published hours (many hotels) have `openingHours: null`.
- `businessStatus` is `operating` when Apple Maps does not mark the place as closed; `temporarily-closed` and `permanently-closed` are Apple's own closure marks.
- Speed: about 1 second per place (measured: 300 places in 309 seconds, all read directly, none retried). A changed place is read twice. The default run timeout is 1 hour, enough for the 1,000-place maximum. The watch memory and the run's position are saved every 50 places and again when Apify signals that it is stopping or moving the run; a run that Apify moves to another server continues from the saved position instead of starting over. If a run is killed without that signal, up to 49 places returned since the last save can come back as new in the next monitor run.
- Two schedules writing the same `watchName` at the same moment can overwrite each other's memory; give each schedule its own watch name.
- This Actor does not search Apple Maps (Apple's robots.txt disallows search pages). Bring the places you want to read.

### Support

Found a place that is read wrongly? Open an issue in the **Issues** tab with the place URL and the run ID.

# Actor input Schema

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

Apple Maps places, one per line: a place URL such as https://maps.apple.com/place?place-id=I918CB7E77B4C7910 or https://maps.apple.com/place?auid=5283835567047557515, an older https://maps.apple.com/?auid=…\&q=… link, or just the place ID (I918CB7E77B4C7910) or auid number. On maps.apple.com open a place and copy the address bar, or use Share > Copy Link. Short share links (maps.apple/p/…) are not read. Up to 1,000 places per run. If empty, three example places are used.

## `onlyChanges` (type: `boolean`):

Return only places whose name, phone, website, address, opening hours, open/closed status (temporarily or permanently closed), rating, number of ratings or main category changed since the last run with the same watch name, plus places new to the watch. Each changed row lists changedFields and the previous values. The first run returns every place as the starting point. An unchanged place is not returned as a row; it is charged only the small place-checked fee.

## `watchName` (type: `string`):

Name of the remembered state used to compare runs (letters, digits, dot, dash, underscore; up to 40). Setting it (or turning on monitor mode) fills changeType, changedFields and previousValues. Use a different name for each list of places you track on its own schedule. With monitor mode on and no name, the name "default" is used.

## `resetMonitoringState` (type: `boolean`):

Start this watch over: forget the remembered places before this run, so every place is returned as a first check.

## Actor input object example

```json
{
  "places": [
    "https://maps.apple.com/place?place-id=I918CB7E77B4C7910",
    "https://maps.apple.com/place?auid=5283835567047557515"
  ],
  "onlyChanges": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per Apple Maps place: name, main category and categories, phone, website, full and structured address, coordinates, time zone, weekly opening hours, open or temporarily or permanently closed status, rating with its scale, provider and number of ratings, price level and amenities. With a watch: changeType, changedFields and the previous values. A missing place, a page that could not be read, an unusable input, a run with no change or a run that hit its maximum charge comes back as a free row that says why.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "places": [
        "https://maps.apple.com/place?place-id=I918CB7E77B4C7910",
        "https://maps.apple.com/place?auid=5283835567047557515"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/apple-maps-place-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 = { "places": [
        "https://maps.apple.com/place?place-id=I918CB7E77B4C7910",
        "https://maps.apple.com/place?auid=5283835567047557515",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/apple-maps-place-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 '{
  "places": [
    "https://maps.apple.com/place?place-id=I918CB7E77B4C7910",
    "https://maps.apple.com/place?auid=5283835567047557515"
  ]
}' |
apify call neverempty/apple-maps-place-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/apple-maps-place-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/i6QsPgHVypB7oc1MX/builds/PW7YnLXBpDFJBOY14/openapi.json
