Places API
API Reference
On this page
Operation
GET /v1/places
Searches place names and scoped addresses near a latitude/longitude point, or lists a category in an area when
category is supplied. The source contract has a readable preview at
OpenAPI YAML.
https://api.openplacesapi.com/v1/places Authenticated public search endpoint for server-side integrations.
Query parameters
Inputs accepted by the search endpoint.
| Name | Type | Required | Details |
|---|---|---|---|
q | string | Usually |
Search text, up to 128 characters after normalization, with at least 3 searchable letters or numbers
(2 for a contiguous CJK query such as 咖啡). Required unless category is supplied.
In the default mode=all, a q that names a kind of place also searches that
category; see category words in q.
|
category | string | No |
Exact Overture category slug; look slugs up with GET /v1/categories.
Matches primary and alternate categories plus hierarchy ancestors of the primary category, so a parent such as
food_and_drink also matches its subcategories. Enables category browse and makes q
optional. Reviewed legacy aliases resolve to the current slug in meta.category, with a
category_alias warning and resolved_category.
Legacy parent aliases such as travel → travel_service,
automotive → automotive_service, and
attractions_and_activities → arts_and_entertainment can match fewer
categories than the old filter; check category_alias and choose a current slug for the scope you need.
An unknown slug returns an unknown_category warning with suggestions.
|
lat | number | Yes | Latitude from -90 to 90. |
lon | number | Yes | Longitude from -180 to 180. |
radius_mi | number | No | Must be greater than 0. Defaults to 25; maximum is 50. |
mode | enum | No | all, name, or address. Defaults to all, which matches
names and addresses and reads category words in q. name matches place names
literally, without category interpretation. address requires at least 4 searchable letters or numbers.
|
limit | integer | No | Minimum 1. Defaults to 10; text search maximum is 20, category browse maximum is 50. |
offset | integer | No | Category-browse pagination offset. offset + limit can be at most 1000, so category browse reaches the first 1,000 places in sort order. Use meta.next_offset for the next page; it is null on the last page. Each page is one billed search. |
min_confidence | number | No | Minimum confidence from 0 to 1. Omit for no confidence filter. |
sort | enum | No | relevance (default) or distance. distance returns the same matches
nearest first, and category pages from offset walk outward from lat/lon.
Add min_confidence so low-confidence records don't fill the nearest results.
|
layer_preset | string | No | Account-owned preset slug. Mutually exclusive with layers. |
layers | string | No | Explicit ordered selection such as base,open,account:client-a. At most 10 account layers. |
Category words in q
A plain q=pharmacy searches the pharmacy category too.
In the default mode=all, when category is not supplied and q names a kind
of place, the search also covers the matching categories. Top-level groups such as shopping or
food_and_drink are never read from q.
A whole-q category phrase such as pharmacy, chemist,
fire station, or pizza near me lists places in the category first (primary-category
and subcategory matches before alternate-category matches; within each, confidence bands of at least 0.9,
at least 0.6 but below 0.9, then below 0.6 (including missing confidence),
nearest first within each band), then other places whose name matches q. Address-only matches are dropped. A category
phrase plus other words, such as boots pharmacy, keeps full-text matches first, then adds places in
the category whose names match the other words.
meta.interpreted_as reports the reading (type, categories,
text, and a message), or is null when q was searched as plain
text. Each result can carry match: name, address, category,
or name_and_category. Use mode=name to match a literal name, and an explicit
category with offset to page through a category, up to the first 1,000 places per query area.
If a search with q finds nothing, it is retried once with a relaxed q: trailing legal
suffixes such as LLC or GmbH, parenthesized asides, and the words and,
the, and of are dropped. Relaxed results must contain the remaining words as whole words
of the place name and carry a query_relaxed warning. If that still finds nothing, q may
be read as the closest category by meaning (for example tienda de computadoras as
computer_store): the results are nearby places in that category, with match: category
and a category_inferred warning.
Responses
Success, quota, and error responses are explicit.
| Status | Response |
|---|---|
200 | Search completed, including zero-result searches. |
400 | Validation error; error.details lists each rejected field. |
401, 403 | Authentication or layer authorization failure. |
402, 429, 503 | Quota exhausted, per-account rate limit exceeded, or temporarily unavailable. |
Only requests with an explicit category can page. Follow meta.next_offset; each page is one billed search.
It can be null because the next page would cross the 1,000-place window even when more places match;
check pagination_limit_reached in meta.warnings.
const apiKey = process.env.OPA_KEY;
if (!apiKey) throw new Error("OPA_KEY is required");
let offset = 0;
do {
const url = new URL("https://api.openplacesapi.com/v1/places");
url.search = new URLSearchParams({
category: "cafe", lat: "40.7128", lon: "-74.0060",
limit: "50", offset: String(offset),
}).toString();
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!response.ok) throw new Error(`Search failed: ${response.status}`);
const body = await response.json();
console.log(body.results);
offset = body.meta.next_offset;
} while (offset !== null);
The rate limit is 120 requests per minute per account for everyone, with at most 8 searches in flight per
account at once. Purchases add searches and extend the whole balance for 12 months. X-Quota-Limit describes the current balance allocation; X-Quota-Remaining is the number left. There is no reset header. Every error body includes a
docs_url linking to its entry on the errors page.
{
"results": [
{
"place_id": "overture:08f2c...",
"name": "Devocion",
"lat": 40.688838,
"lon": -73.979914,
"distance_mi": 2.15,
"category": "cafe",
"categories": ["cafe", "coffee_shop"],
"category_hierarchy": ["food_and_drink", "casual_eatery", "cafe"],
"address": {
"locality": "Brooklyn",
"country_code": "US"
},
"phone": "+17185551212",
"website": "https://www.devocion.com",
"confidence": 0.92,
"operating_status": "operational",
"match": "category"
}
],
"meta": {
"request_id": "00000000-0000-4000-8000-000000000000",
"data_source": "overture",
"data_release": "2026-10-21.0",
"data_releases": ["2026-10-21.0"],
"q": "coffee",
"category": null,
"min_confidence": 0.85,
"sort": "relevance",
"mode": "all",
"lat": 40.7128,
"lon": -74.006,
"radius_mi": 25,
"limit": 10,
"offset": 0,
"next_offset": null,
"interpreted_as": {
"type": "category",
"categories": ["coffee_shop", "cafe"],
"text": null,
"message": "Read \"coffee\" as the category coffee_shop (also cafe): places in that category come first, then other name matches. Use mode=name to match names only, or category=coffee_shop with offset to page through every match."
},
"warnings": [],
"layer_selection": {
"source": "default",
"preset": null,
"layers": [{ "type": "overture_base" }, { "type": "open" }]
}
}
} Warnings
Read meta.warnings before giving up.
Successful responses can carry warnings. On an empty first page, unknown_category lists the closest
known categories in suggestions, and no_results_within_radius reports
nearest_match_distance_mi and a nearest_match object (public id, name, distance_mi, and locality and country when available) when a match is found within 100 miles. That place stays outside results, with a message naming the
radius_mi to retry with. query_relaxed means q had no exact matches and
the results are for the relaxed text named in the message. category_inferred means nothing matched by
name, so the results are nearby places in the closest category by meaning. pagination_limit_reached means more places
match than a category browse can page through, so next_offset is null before the end of
the results. See
search warnings for every code.
| Warning | Meaning and client action |
|---|---|
category_alias |
A reviewed legacy category was resolved to resolved_category, also returned in
meta.category. This warning can appear on any successful page. Legacy parent aliases
can narrow the old filter; check the resolved slug against the scope you need.
|
Categories
GET /v1/categories
Lists valid category values from the Overture category taxonomy, including parent groups. It is
public, needs no API key, does not count against quota, and is cacheable for an hour. Without q it
returns every category alphabetically; with q (1 to 64 characters) it returns the best matches,
20 by default. limit accepts 1 to 100. Unknown parameters return
validation_error.
https://api.openplacesapi.com/v1/categories Category lookup for building category requests.
curl "https://api.openplacesapi.com/v1/categories?q=coffee&limit=3" {
"categories": [
{
"category": "coffee_shop",
"label": "Coffee shop",
"parent": "non_alcoholic_beverage_venue",
"hierarchy": [
"food_and_drink",
"non_alcoholic_beverage_venue",
"coffee_shop"
]
},
{
"category": "coffee_roastery",
"label": "Coffee roastery",
"parent": "coffee_shop",
"hierarchy": [
"food_and_drink",
"non_alcoholic_beverage_venue",
"coffee_shop",
"coffee_roastery"
]
},
{
"category": "coffee_and_tea_supplies",
"label": "Coffee and tea supplies",
"parent": "food_and_beverage_store",
"hierarchy": [
"shopping",
"food_and_beverage_store",
"coffee_and_tea_supplies"
]
}
],
"meta": {
"request_id": "00000000-0000-4000-8000-000000000000",
"q": "coffee",
"count": 3,
"total": 2201
}
}