Nearest Places for GPS Coordinates
Turns a pair of GPS coordinates into the nearest named place — city, region, province, community and country — and lists the other places within a radius. One pair or up to 50 pairs in a single call. It is what Opensolr Photos uses to write a photo's EXIF position into words, and it works the same for any application that has coordinates and wants a place name next to them.
Endpoint
POST https://opensolr.com/solr_manager/api/nearby_places
opensolr.com. It is part of the management API, not the AI API, so do not call it on api.opensolr.com — that host answers 404 with WRONG_API_HOST and tells you the correct URL. GET works exactly like POST; a JSON request body is accepted too, with the same field names.Parameters
| Parameter | Type | Status | Description |
|---|---|---|---|
email | string | Required | Your Opensolr registration email address |
api_key | string | Required | Your Opensolr API key (master key, or a scoped key that allows this endpoint) |
coords | string or array | Optional | One pair as lat,lon in decimal degrees, or several pairs separated by ; (a new line or | works too), or a JSON array of pairs. Several pairs switch the answer to the batch envelope below |
lat, lon | number | Optional | One pair as two separate fields, instead of coords |
within | integer | Optional | Radius in kilometres to look for places in: 1 to 100, default 25 |
coords or lat + lon is required. A request naming no usable pair answers {"status": false, "msg": "ERROR_INVALID_COORDS"} together with max_coords. Latitude must be within ±90 and longitude within ±180; exactly 0,0 is refused as well, because it is where broken EXIF data points, not where a photo was taken.How It Works
Each pair is rounded to four decimals (about 10 metres) and answered with the named places within the radius, nearest first, from a world-wide postal-place database. The nearest one is also returned on its own as nearest, which is the field an application usually wants.
Answers are permanent. Geography does not change on the scale of an application: once a pair has been answered it is kept, positive answer or “nothing within the radius” alike, and every later request for the same pair and radius — from any account — is served from that store without touching the place database. Only an upstream failure is remembered briefly (five minutes), so a hiccup never masquerades as “no place here”.
In a batch, every pair is looked up on its own: the ones already known come from the store, the unknown ones go to the place database together in one round trip, and each is answered under its own key, so one pair in the ocean never voids the other forty-nine.
Absent facts are absent
On a place entry only place, city, country_code and distance_km are guaranteed. region, province, community and country are omitted when the database does not know them for that place — never an empty string. Test with isset().
place is the name as the database has it, which for a large city is a postal-office name such as Bucureşti 15; city is the same name with the trailing office number removed, which is the field to show and to index.
Versioned contract
The response shape is Opensolr's, not the database's, and it carries contract_version. The provider behind the endpoint can be replaced without breaking your integration.
Limits
| Limit | Value | What happens |
|---|---|---|
| Pairs per call | 50 | Extras are dropped; the first 50 are answered |
| Radius | 1 to 100 km | Anything outside is clamped, default 25 |
| Places per pair | 20 | Nearest first |
| Rate limit cost | 1 per request | Counted once against the per-minute, per-hour and monthly API request limits, whatever the number of pairs. This endpoint does not draw on the AI allowance |
Response
A single pair answers the place envelope directly:
| Key | Type | Description |
|---|---|---|
status | boolean | true when at least one place was found within the radius |
contract_version | integer | Version of this response shape. Currently 1 |
coords | string | The pair as answered, rounded to four decimals |
within_km | integer | The radius in force |
count | integer | Number of entries in places |
nearest | object | The first entry of places |
places | array | Up to 20 place entries, nearest first |
A place entry:
| Key | Type | Always present | Description |
|---|---|---|---|
place | string | Yes | The place name as the database has it |
city | string | Yes | The place name without a trailing postal-office number |
country_code | string | Yes | Two-letter ISO 3166 country code, uppercase |
distance_km | float | Yes | Distance from the pair, one decimal |
region | string | No | Region, state or county |
province | string | No | Province, where the country has them |
community | string | No | Community, district or sector |
country | string | No | Country name |
Several pairs answer the batch envelope: status: true, contract_version, within_km, count, and results — an object with one place envelope (or one error entry) per pair, keyed by the pair rounded to four decimals.
Errors
| Code | Where | Meaning |
|---|---|---|
ERROR_GEO_NO_DATA | entry | No named place within the radius (open sea, polar regions). A statement about the pair; remembered for good like any answer |
ERROR_GEO_LOOKUP_UNAVAILABLE | entry | The place database did not answer. Retry — remembered for five minutes only |
ERROR_INVALID_COORDS | request | No usable pair in the request. HTTP 200 with status: false |
ERROR_AUTHENTICATION_FAILED | request | HTTP 403. Email / API key mismatch |
ERROR_SCOPED_KEY_ENDPOINT_NOT_ALLOWED | request | HTTP 403. A scoped key that was not granted this endpoint |
ERROR_RATE_LIMIT_PER_MINUTE / _PER_HOUR | request | HTTP 429 with a Retry-After header. Back off and retry |
WRONG_API_HOST | request | HTTP 404. You called it on api.opensolr.com; the body names the correct URL |
Code Examples
cURL
# One pair curl -s "https://opensolr.com/solr_manager/api/nearby_places?email=YOUR_EMAIL&api_key=YOUR_API_KEY&coords=44.4268,26.1025" # One pair, as two fields, with a 10 km radius curl -s -X POST "https://opensolr.com/solr_manager/api/nearby_places" \ -d "email=YOUR_EMAIL" -d "api_key=YOUR_API_KEY" \ -d "lat=46.7712" -d "lon=23.6236" -d "within=10" # A batch: pairs separated by semicolons curl -s -X POST "https://opensolr.com/solr_manager/api/nearby_places" \ -d "email=YOUR_EMAIL" -d "api_key=YOUR_API_KEY" \ -d "coords=44.4268,26.1025;46.7712,23.6236;0.5,0.5"
PHP
$payload = json_encode([ 'email' => 'YOUR_EMAIL', 'api_key' => 'YOUR_API_KEY', 'coords' => ['44.4268,26.1025', '46.7712,23.6236'], 'within' => 25, ]); $ch = curl_init('https://opensolr.com/solr_manager/api/nearby_places'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => $payload, ]); $out = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($out['results'] as $pair => $entry) { if (empty($entry['status'])) { echo "{$pair}: {$entry['msg']}\n"; continue; } $near = $entry['nearest']; // region and country may be absent: absent means unknown. $where = implode(', ', array_filter([$near['city'], $near['region'] ?? null, $near['country'] ?? null])); echo "{$pair}: {$where} ({$near['distance_km']} km)\n"; }
Python
import requests r = requests.post( "https://opensolr.com/solr_manager/api/nearby_places", json={ "email": "YOUR_EMAIL", "api_key": "YOUR_API_KEY", "coords": ["44.4268,26.1025", "46.7712,23.6236"], }, timeout=30, ) for pair, entry in r.json()["results"].items(): if not entry.get("status"): print(pair, entry["msg"]) continue near = entry["nearest"] # .get() for the optional fields: an absent key means the database did not know it. print(pair, near["city"], near.get("region"), near.get("country"), near["distance_km"], "km")
Example Response
A single pair:
{ "status": true, "contract_version": 1, "coords": "46.7712,23.6236", "within_km": 25, "count": 20, "nearest": { "place": "Cluj-Napoca", "city": "Cluj-Napoca", "country_code": "RO", "distance_km": 1.9, "region": "Cluj", "country": "Romania" }, "places": [ { "place": "Cluj-Napoca", "city": "Cluj-Napoca", "country_code": "RO", "distance_km": 1.9, "region": "Cluj", "country": "Romania" }, { "place": "Feleacu", "city": "Feleacu", "country_code": "RO", "distance_km": 6.1, "region": "Cluj", "country": "Romania" } ] }
A batch, with one pair in the Gulf of Guinea:
{ "status": true, "contract_version": 1, "within_km": 25, "count": 2, "results": { "44.4268,26.1025": { "status": true, "contract_version": 1, "coords": "44.4268,26.1025", "within_km": 25, "count": 20, "nearest": { "place": "Bucureşti 15", "city": "Bucureşti", "country_code": "RO", "distance_km": 4.2, "region": "Bucureşti", "province": "Bucureşti", "community": "Sector 5", "country": "Romania" }, "places": [ "..." ] }, "0.5000,0.5000": { "status": false, "msg": "ERROR_GEO_NO_DATA", "coords": "0.5000,0.5000", "within_km": 25 } } }
Use Cases
- Write the place a photo, a check-in or a sensor reading was taken at into your index in words, so that Lisbon is a search, a filter and an autocomplete entry — exactly what Opensolr Photos does at indexing time
- Show “Cluj-Napoca, Romania” instead of
46.7712,23.6236anywhere a coordinate pair would otherwise be printed - Group records by city, region or country without a geocoding subscription: the answers are permanent and shared, so a busy application pays for a position once, ever
- Pair it with geo_lookup: that one turns an address into coordinates, this one turns coordinates into the nearest named place
Related Documentation
Need help with the Opensolr API? We are here to help.
Contact Support