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'))