# Multi-Site Proximity Matcher — Places to Stores & Branches (`mrtronson/multi-site-proximity-matcher`) Actor

Match existing Google Maps or geocoded places to every nearby store, branch or property. Preserve overlapping site matches, or assign the nearest site. Export distances and per-site counts. $0.05 per report, up to 10,000 places and 200 sites.

- **URL**: https://apify.com/mrtronson/multi-site-proximity-matcher.md
- **Developed by:** [Typed Diff](https://apify.com/mrtronson) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 delivered reports

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

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

### Match one place list to many stores, branches or properties

**Turn an existing geocoded place list into a table of which places are near each of your sites.** Match restaurants to retail locations, competitors to stores, or amenities to a property shortlist. Keep every overlapping match or assign each place to its nearest site.

Paste two lists and run. The Actor accepts Compass Google Maps coordinates directly and needs no mapping API key, geocoding call or new scrape. Each output match preserves the place ID, your site ID, distance and rank so it can be joined back to your original data.

### Why a multi-site matcher?

Combining multiple geographic searches can produce a useful set of places while losing the relationship between each result and the original search sites. Separate overlapping searches can also return the same place repeatedly. This Actor reconstructs the many-to-many relationship from the supplied coordinates in one bounded batch.

It is designed for site portfolios rather than a single rectangular territory filter. A place within range of three stores can produce three matches. In nearest mode, it produces one match. Every site appears in the separate site summary, including sites with zero matches.

### Quick start

1. Paste existing place rows into **Places with coordinates**. Supported shapes include Compass `location.lat` / `location.lng`, top-level `lat` / `lng`, or `latitude` / `longitude`.
2. Supply each site with a unique string `id`, coordinates and an optional `title`.
3. Set a radius in meters. The default is 5,000 meters.
4. Choose all nearby sites or nearest site only, then run.

The prefilled example is explicitly synthetic. It demonstrates one place assigned to two nearby sites in Oslo; it does not discover businesses. Replace both lists for your own analysis.

### Example input

```json
{
  "places": [{"placeId":"demo-place","title":"Synthetic venue","location":{"lat":59.9139,"lng":10.7522}}],
  "sites": [
    {"id":"site-a","title":"Synthetic site A","lat":59.91,"lng":10.75},
    {"id":"site-b","title":"Synthetic site B","lat":59.92,"lng":10.76}
  ],
  "radiusMeters": 5000,
  "mode": "all"
}
```

### Output

The dataset contains `placeId`, `placeTitle`, `inputIndex`, `siteId`, `siteTitle`, `distanceMeters`, `rank`, `status` and `reason`. Export to JSON, CSV or Excel. `SITES` contains per-site match counts. `OUTPUT` contains the complete report and summary.

Status is `matched`, `unmatched`, `review` or `duplicate`. Missing or invalid coordinates are explicitly held for review. Repeated place IDs with identical coordinates are counted once; conflicting coordinates for the same ID are held for review. Rows without IDs remain independent input rows. Invalid or duplicate site IDs fail the run before matching.

Ranks are based on unrounded distance. Exact distance ties are ordered by site ID for reproducibility. Input rows are preserved by `inputIndex`; a matched place may have multiple output rows in all-sites mode.

### Pricing and limits

$0.05 per delivered matching report, including up to 10,000 places and 200 sites. Platform usage is included under the published pay-per-event model. No separate charge per overlapping match or third-party API charge. The input limit is 12 MB and the output ceiling is 100,000 rows. If the complete output would exceed that ceiling, the run fails without the matching event; reduce the radius or choose nearest mode.

Invalid top-level input does not generate the billing event. An entirely unmatched report is still a completed report and costs $0.05.

### Distance and coverage

Distances are approximate great-circle distances using the mean Earth radius. They are straight-line distances, not driving distance, journey time, administrative borders, service areas or proof of market coverage. Dateline crossing is supported. The radius includes its boundary before display rounding. Source coordinate accuracy may dominate distance precision.

The Actor evaluates only the data supplied. It neither finds missing venues nor guarantees that your source dataset is complete. No user data is sent to another service, and the original catalog is never changed. Use the Apify API, saved tasks or integrations to connect the matching step to your existing workflow. Report reproducible issues through the Issues tab.

# Actor input Schema

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

Compass location.lat/lng or top-level latitude/longitude, lat/lng. Existing placeId or id enables deduplication.

## `sites` (type: `array`):

Each site needs a unique string id and coordinates. Maximum 200.

## `radiusMeters` (type: `integer`):

Maximum approximate great-circle distance, not driving distance.

## `mode` (type: `string`):

Keep every site within the radius, or choose only the nearest. Ties are ordered by site ID.

## Actor input object example

```json
{
  "places": [
    {
      "placeId": "synthetic-place",
      "title": "Synthetic example venue",
      "location": {
        "lat": 59.9139,
        "lng": 10.7522
      }
    }
  ],
  "sites": [
    {
      "id": "oslo-a",
      "title": "Synthetic site A",
      "lat": 59.91,
      "lng": 10.75
    },
    {
      "id": "oslo-b",
      "title": "Synthetic site B",
      "lat": 59.92,
      "lng": 10.76
    }
  ],
  "radiusMeters": 5000,
  "mode": "all"
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `queue` (type: `string`):

No description

## `report` (type: `string`):

No description

# 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": [
        {
            "placeId": "synthetic-place",
            "title": "Synthetic example venue",
            "location": {
                "lat": 59.9139,
                "lng": 10.7522
            }
        }
    ],
    "sites": [
        {
            "id": "oslo-a",
            "title": "Synthetic site A",
            "lat": 59.91,
            "lng": 10.75
        },
        {
            "id": "oslo-b",
            "title": "Synthetic site B",
            "lat": 59.92,
            "lng": 10.76
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrtronson/multi-site-proximity-matcher").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": [{
            "placeId": "synthetic-place",
            "title": "Synthetic example venue",
            "location": {
                "lat": 59.9139,
                "lng": 10.7522,
            },
        }],
    "sites": [
        {
            "id": "oslo-a",
            "title": "Synthetic site A",
            "lat": 59.91,
            "lng": 10.75,
        },
        {
            "id": "oslo-b",
            "title": "Synthetic site B",
            "lat": 59.92,
            "lng": 10.76,
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("mrtronson/multi-site-proximity-matcher").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": [
    {
      "placeId": "synthetic-place",
      "title": "Synthetic example venue",
      "location": {
        "lat": 59.9139,
        "lng": 10.7522
      }
    }
  ],
  "sites": [
    {
      "id": "oslo-a",
      "title": "Synthetic site A",
      "lat": 59.91,
      "lng": 10.75
    },
    {
      "id": "oslo-b",
      "title": "Synthetic site B",
      "lat": 59.92,
      "lng": 10.76
    }
  ]
}' |
apify call mrtronson/multi-site-proximity-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mrtronson/multi-site-proximity-matcher"
        }
    }
}
```

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/IZHeGdQmX0OxV65j7/builds/HhXTEjw2eV5OLB4HJ/openapi.json
