Reprompt
Guides

Placematch

Match places across multiple geospatial data sources using AI-powered entity resolution

Overview

The /placematch endpoint helps you match a place against multiple geospatial data sources using AI-powered entity resolution. Given information about a place (name, address, coordinates), we find the right match across sources like Reprompt, Overture, Foursquare, or your own data.

Under the hood, we use a combination of:

  • Name similarity
  • Geographic proximity and distance
  • Category alignment
  • Address component matching
  • Fine-tuned LLM verification

Each match includes a confidence score, source attribution, and reasoning explanation.

Basic Matching

Name + Coordinates

Match a place using just its name and coordinates. This is useful when you don't have a full address.

curl --request POST \
  --url https://api.reprompt.io/v2/placematch \
  --header 'Authorization: Bearer {YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '{
    "place": {
      "name": "Starbucks",
      "latitude": 40.7614327,
      "longitude": -73.9776216
    },
    "match_sources": ["reprompt"]
  }'
import requests

response = requests.post(
    'https://api.reprompt.io/v2/placematch',
    headers={
        'Authorization': 'Bearer {YOUR_API_KEY}',
        'Content-Type': 'application/json'
    },
    json={
        'place': {
            'name': 'Starbucks',
            'latitude': 40.7614327,
            'longitude': -73.9776216
        },
        'match_sources': ['reprompt']
    }
)

matches = response.json()

Response:

{
  "results": [
    {
      "place_id": "288a25a3-6584-510e-82da-7ebafc328358",
      "name": "Starbucks",
      "full_address": "1290 6th ave, new york, ny 10104",
      "latitude": 40.76075,
      "longitude": -73.97885,
      "category_primary": "coffee_shop",
      "phone": "+1 212-977-4861",
      "website": "https://www.starbucks.com/store-locator/store/88886/",
      "source": "reprompt",
      "distance_m": 128.322674,
      "is_match": true,
      "confidence": "VERY_HIGH",
      "reasoning": "The input POI name is exactly 'Starbucks' and the found POI name is the same. The distance between POIs is 128 meters."
    }
  ]
}

GERS Matching with Overture

The Global Entity Reference System (GERS) provides stable UUID identifiers for places in Overture's 64M+ place dataset. Use placematch to get GERS IDs for your places, enabling data joins and stable references across Overture releases.

Getting GERS IDs

curl --request POST \
  --url https://api.reprompt.io/v2/placematch \
  --header 'Authorization: Bearer {YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '{
    "place": {
      "name": "Blue Bottle Coffee",
      "full_address": "66 Mint St, San Francisco, CA 94103",
      "latitude": 37.7814,
      "longitude": -122.4071
    },
    "match_sources": ["overture"],
    "max_matches": 1
  }'
import requests

response = requests.post(
    'https://api.reprompt.io/v2/placematch',
    headers={
        'Authorization': 'Bearer {YOUR_API_KEY}',
        'Content-Type': 'application/json'
    },
    json={
        'place': {
            'name': 'Blue Bottle Coffee',
            'full_address': '66 Mint St, San Francisco, CA 94103',
            'latitude': 37.7814,
            'longitude': -122.4071
        },
        'match_sources': ['overture'],
        'max_matches': 1
    }
)

if response.ok and response.json()['results']:
    match = response.json()['results'][0]
    gers_id = match['place_id']
    print(f"GERS ID: {gers_id}")
    print(f"Confidence: {match['confidence']}")

The place_id field contains the GERS ID when matching against Overture.

Foursquare OS Places Matching

Match places against Foursquare OS Places to get FSQ IDs:

curl --request POST \
  --url https://api.reprompt.io/v2/placematch \
  --header 'Authorization: Bearer {YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '{
    "place": {
      "name": "Blue Bottle Coffee",
      "full_address": "66 Mint St, San Francisco, CA 94103",
      "latitude": 37.7814,
      "longitude": -122.4071
    },
    "match_sources": ["foursquare"]
  }'
import requests

response = requests.post(
    'https://api.reprompt.io/v2/placematch',
    headers={
        'Authorization': 'Bearer {YOUR_API_KEY}',
        'Content-Type': 'application/json'
    },
    json={
        'place': {
            'name': 'Blue Bottle Coffee',
            'full_address': '66 Mint St, San Francisco, CA 94103',
            'latitude': 37.7814,
            'longitude': -122.4071
        },
        'match_sources': ['foursquare']
    }
)

if response.ok and response.json()['results']:
    match = response.json()['results'][0]
    fsq_id = match['place_id']
    print(f"FSQ ID: {fsq_id}")

The place_id field contains the FSQ ID when matching against Foursquare.

Combined Matching: Check Multiple Data Sources

Match against both Overture (open data) and Foursquare simultaneously using the match_sources parameter.

Use cases:

  • Data quality checks: Verify if your places are represented in authoritative open datasets
  • Coverage analysis: Understand which places are missing from public data sources
  • Multi-source enrichment: Get both GERS IDs and FSQ IDs in one API call
import requests

response = requests.post(
    'https://api.reprompt.io/v2/placematch',
    headers={'Authorization': 'Bearer {YOUR_API_KEY}'},
    json={
        'place': {
            'name': 'Blue Bottle Coffee',
            'full_address': '66 Mint St, San Francisco, CA 94103',
            'latitude': 37.7814,
            'longitude': -122.4071
        },
        'match_sources': ['overture', 'foursquare']
    }
)

results = response.json()['results']

for result in results:
    if result['source'] == 'overture':
        print(f"✓ Found in Overture - GERS ID: {result['place_id']}")
    elif result['source'] == 'foursquare':
        print(f"✓ Found in Foursquare - FSQ ID: {result['place_id']}")
{
  "results": [
    {
      "place_id": "55d8c5f4-7b23-4713-ab5e-135445190e2d",
      "name": "Blue Bottle Coffee",
      "source": "overture",
      "confidence": "VERY_HIGH"
    },
    {
      "place_id": "49ca8f4df964a520b9581fe3",
      "name": "Blue Bottle Coffee",
      "source": "foursquare",
      "confidence": "VERY_HIGH"
    }
  ],
  "metadata": {
    "total_results": 2,
    "sources_searched": ["overture", "foursquare"],
    "search_radius_meters": 1000.0
  }
}

Batch Processing: Matching Open Datasets

Match 1000+ places from an open dataset against Overture and Foursquare using async requests with automatic retry.

Async Script with Retry

Install dependencies: pip install pandas aiohttp backoff

import asyncio, os, pandas as pd, aiohttp, backoff

API_URL = "https://api.reprompt.io/v2/placematch"
API_KEY = os.getenv("REPROMPT_API_KEY")

@backoff.on_exception(
    backoff.expo, aiohttp.ClientError, max_tries=5,
    giveup=lambda e: isinstance(e, aiohttp.ClientResponseError) and e.status not in [429, 500, 502, 503]
)
async def match_place(session, name, address, lat=None, lon=None):
    place = {"name": name, "full_address": address}
    if lat and lon:
        place.update({"latitude": float(lat), "longitude": float(lon)})

    async with session.post(API_URL, json={
        "place": place,
        "match_sources": ["overture", "foursquare"]
    }) as resp:
        resp.raise_for_status()
        return await resp.json()

async def match_dataset(csv_file):
    df = pd.read_csv(csv_file).head(1000)
    semaphore = asyncio.Semaphore(10)
    headers = {"Authorization": f"Bearer {API_KEY}"}

    async def match_row(row):
        async with semaphore:
            try:
                resp = await match_place(
                    session,
                    row['business_name'],
                    f"{row['business_address']}, {row['business_city']}, {row['business_state']}",
                    row.get('business_latitude'),
                    row.get('business_longitude')
                )
                by_source = {}
                for m in resp.get('results', []):
                    pid = m.get('place_id', '')
                    if '-' in pid: by_source.setdefault('overture', []).append(m)
                    elif len(pid) == 24: by_source.setdefault('foursquare', []).append(m)

                return {
                    'name': row['business_name'],
                    'overture_id': by_source.get('overture', [{}])[0].get('place_id'),
                    'foursquare_id': by_source.get('foursquare', [{}])[0].get('place_id')
                }
            except Exception as e:
                print(f"Error: {row['business_name']}: {e}")
                return None

    async with aiohttp.ClientSession(headers=headers) as session:
        tasks = [match_row(row) for _, row in df.iterrows()]
        results = [r for r in await asyncio.gather(*tasks) if r]

    pd.DataFrame(results).to_csv('matched.csv', index=False)
    print(f"Matched {len(results)} places")

asyncio.run(match_dataset('restaurants.csv'))