Find what’s nearby — the ArcGIS Places service, from search to place details
“Show me the coffee shops within a ten-minute walk” is one of the most common feature requests in any map-facing application — and one of the easiest to get wrong. Scrape a POI dataset and it goes stale; bolt on a third-party places API and you are juggling a second vendor, a second billing model, and a second set of terms. If you are already building on the Esri stack, the ArcGIS Places service answers the question natively: a hosted location service that searches businesses and geographic places worldwide, filters them by category and text, and returns per-place detail down to phone, website, and social handles.
The service is straightforward to call, but its billing architecture rewards developers who
understand it — search requests and attribute requests are priced separately, and attribute fields are
grouped into price tiers you opt into field-by-field. Get the pattern right and a places feature costs
pennies; request all fields on every click and you are paying for data you never
render. Here is how the service is put together, the working code, and the cost-control patterns we use in
client apps.
What the Places service is — and where it runs
The Places service is one of the ArcGIS Location Services, alongside basemap styles, geocoding, routing, GeoEnrichment, and elevation. It lives at a single REST endpoint:
https://places-api.arcgis.com/arcgis/rest/services/places-service/v1
Every request authenticates with an access token — an API key credential or OAuth 2.0 token — that carries the places service privilege. Results include category metadata and icon resources so you can style what comes back without shipping your own symbol set.
Five requests, one mental model
The whole service surface is five requests. Two find places, one enriches them, and two navigate the category tree:
| Request | What it does | Price category |
|---|---|---|
/places/near-point | Nearby search: places within a radius of a point | Search |
/places/within-extent | Bounding-box search: places inside an extent | Search |
/places/{placeId} | Place details: attributes for one place, field by field | Attribute groups |
/categories | List or filter the category vocabulary | Free |
/categories/{categoryId} | Details for one category | Free |
The intended flow: a search returns lightweight results — placeId,
name, categories, location,
and distance — and the placeId is your ticket to
the details endpoint when, and only when, a user actually asks for more.
Nearby search: radius, categories, text
A nearby search takes an x/y in WGS84 decimal
degrees plus a radius in meters. The default radius is 1,000 m and the maximum is 10,000 m. Esri’s
own best-practice note says it plainly: always refine with categoryIds and/or
searchText rather than pulling everything in range.
In the ArcGIS Maps SDK for JavaScript, the places module wraps the request in
one call:
import * as places from "@arcgis/core/rest/places.js"; // coffee within a 10-minute walk of downtown Ann Arbor const results = await places.queryPlacesNearPoint({ point: { x: -83.7430, y: 42.2808 }, radius: 800, // meters; default 1000, max 10000 categoryIds: ["4bf58dd8d48988d16d941735"], // Cafe searchText: "coffee" }); for (const place of results.results) { console.log(place.name, place.distance, place.placeId); }
Each page returns at most 20 places. If more match, the response carries paging links — in REST you
follow pagination.nextUrl (re-attaching your token); in the JS SDK you call
nextPage() on the response while nextPage is valid.
Paging tops out at 200 places per search. If you are hitting that ceiling, Esri’s guidance is to shrink
the radius or tighten the category filter — a places UI that needs more than 200 pins probably needs a
better question, not more results.
Place details: pay only for the fields you name
The details endpoint is where the design gets interesting. You pass a placeId
and a requestedFields array naming exactly the attributes you want back:
const details = await places.fetchPlace({ placeId: "bd5f5dfa788b7c5f59f3bfe2cc3d9c60", requestedFields: [ "name", "address:streetAddress", "contactInfo:telephone", "contactInfo:website" ] });
Fields are organized into four price groups, and you are billed once per group touched, no matter how many fields you request from that group:
| Price group | Example fields | Location Platform price |
|---|---|---|
| Place | name, categories | 100 free, then $0.05 / 1,000 places |
| Location | location | 100 free, then $0.35 / 1,000 places |
| Address | address:streetAddress, address:locality, address:postcode… | 100 free, then $0.10 / 1,000 places |
| Details | contactInfo:*, socialMedia:* | 100 free, then $0.13 / 1,000 places |
Searches themselves are priced separately: 500 free per month, then $8.00 per 1,000 on Location Platform.
Category requests are free. Two more billing rules matter in practice: you are only charged for attributes
that come back with valid values (nulls are free), and requesting two fields from two different
groups bills both groups. The anti-pattern is requestedFields: "all" — the
docs support it, and the docs also warn against it, because you are charged for every group with data even if
your UI shows two lines.
null
— including hours:*, rating:*,
description, chains, and the
additionalLocations group. Nulls are not billed, but design your details panel
around the fields that reliably populate today: name, categories, location, address, phone, website, and
social handles. Check the service’s release notes before building UI on the currently-null fields.Patterns that keep a places feature premium — and cheap
- Search shallow, detail on demand. Render search results from the search response alone
— it already carries name, category, location, and distance. Call
fetchPlaceonly when a user opens a specific place, and request only the fields that panel shows. - Cache the category tree, not the places. The category endpoints are free and the vocabulary is stable within a session — load it once for your filter UI. The terms of use are explicit that places and place details themselves cannot be permanently stored, so treat every result as display-time data, not a dataset you ETL into PostGIS.
- Use the service icons. Results ship with icon resources per category — a consistent, professionally drawn symbol set you do not have to design or license.
- Scope the token. Mint an API key credential with only the places privilege for this workload, referrer-locked to your domain, with a real expiration — same secrets hygiene as any other location service key.
Where this lands in a real app
We reach for the Places service when an application needs context around a location the user already cares about: amenities near a property in a parcel explorer, services around a proposed site in a suitability tool, points of interest along a route. It replaces a third-party POI dependency with a service that speaks the same authentication, the same terms, and the same SDK as the rest of the stack — one vendor, one token store, one invoice. And because the billing is per-use with a free tier, a places feature can ship in a prototype for $0 and scale linearly with real usage.
References
- ArcGIS Places service — REST API overview (service URL, request table, pricing, terms of use)
- Nearby search — Esri Developer documentation (radius limits, paging, billing notes, code examples)
- Get place details — Esri Developer documentation (requestedFields, price groups, null-field billing)
- How to build a place finding app — Esri Developer documentation
- Tutorial: Find nearby places and details — ArcGIS Maps SDK for JavaScript
Want a places feature that feels native to your app?
We build branded, production-grade ArcGIS applications on Location Platform services — places, geocoding, routing — with the token hygiene and cost controls already wired in.
Book a free intro call