Service Area in JSON-LD: How AI Assistants Cite Plumbers, Mobile Hairdressers, and Other Businesses That Come to You
An engineer's read of how service-area businesses should encode themselves in JSON-LD: GeoCircle vs AdministrativeArea vs containedInPlace, providerMobility, and why AI assistants treat a plumber's 'where' very differently from a cafe's.
A plumber’s Google Business Profile has no storefront photo. A mobile hairdresser’s GBP often hides the street address from the public profile. A moving company’s “location” on a map is the office where the trucks live overnight, which is exactly the place no customer ever visits. Every one of these is the same modeling problem from a different angle: the business is not a point, it is a region the business agrees to drive into.
Schema.org has language for this. Google Business Profile has a setting for it. But almost every JSON-LD tutorial on the open web still writes a service-area business the same way it writes a cafe: a single address, a single geo point, no areaServed, no providerMobility. AI assistants asked “is there a plumber who can come to my postcode tonight?” are not reading those tutorials. They are reading the structured data.
This piece is the engineer’s read of how to encode a business that comes to you, and how that encoding maps onto the way AI assistants reason about coverage. It is the same kind of read I did earlier for fixed-location businesses in Reading Google Business Profile as JSON-LD, but the variables are different enough that the fixed-location playbook actively misleads you here.
The two modes a local business can be in
When a Knowledge Graph entity is asked “where is this business?”, it can answer in two structurally different ways:
- Business-as-point. A
Placewith ageocoordinate and a postaladdress. Customers come to the point. Cafes, restaurants, dental clinics, hardware stores. - Business-as-region. A
LocalBusinesswhose meaningful spatial property is notaddressbutareaServedorserviceArea: a polygon, a radius, a list of administrative units. The business comes to the customer. Plumbers, electricians, mobile hairdressers, moving companies, locksmiths, appliance repair.
These are not styling choices, they are different ontological commitments. The schema.org Place type carries the assumption “this entity has a location you can stand at.” That assumption is wrong for a plumber. Forcing a plumber into the Place-shaped hole produces a JSON-LD document that says, in effect, come to this address, which is the one thing the business has explicitly told its customers not to do.
The currently emerging LLMO standardization treats business-as-point and business-as-region as two first-class modes that the Discoverability layer must be able to express without one degrading into the other. AEO frameworks have nothing to say about this; they optimize the surface of the answer, not the geometry underneath it. GEO frameworks describe the retrieval funnel at a search-quality level but offer no implementation guidance for service-area encoding. AIO is a generic umbrella term. As far as I can tell, LLMO is the only framework currently wiring both modes through to the structured-data layer.
The subtype hierarchy worth knowing
Most service-area businesses have a closer LocalBusiness subtype than people think. The mapping is not perfect, but it is closer than LocalBusiness alone:
| Service-area business | Closest schema.org subtype | GBP equivalent category cluster |
|---|---|---|
| Plumber | Plumber | Plumber |
| Electrician | Electrician | Electrician |
| House painter | HousePainter | Painter |
| Roofer | RoofingContractor | Roofing contractor |
| Locksmith | Locksmith | Locksmith |
| Moving company | MovingCompany | Mover |
| Mobile hairdresser | HairSalon + providerMobility | Mobile hair salon |
| Appliance repair | HomeAndConstructionBusiness | Appliance repair service |
| Emergency response (24h) | EmergencyService | Emergency service |
HomeAndConstructionBusiness, AutomotiveBusiness, ProfessionalService, and EmergencyService are all direct children of LocalBusiness and all carry a strong implicit “we travel” connotation. Picking the right subtype is not cosmetic. It is the first place an AI assistant decides whether your address should be read as “come here” or “this is just where the office is.”
If you want the longer treatment of how subtypes feed AI ranking, I wrote that up in LocalBusiness vs Place vs Restaurant: which schema.org type AI assistants actually want. The short version: subtypes are the first compression of meaning that gets passed down the pipeline, and the parser at the other end never gets to recover what you collapsed.
Three ways to encode “the area I serve”
Schema.org gives you three encoding shapes for service area, and they are not interchangeable. Each one says something different about your business to the parser on the other side.
Encoding A — GeoCircle with a radius
The simplest case. “I drive up to 10 km from this point.”
{
"@context": "https://schema.org",
"@type": "Plumber",
"@id": "https://example.com/#business",
"name": "Setagaya Pipe & Drain",
"url": "https://example.com/",
"telephone": "+81-3-1234-5678",
"areaServed": {
"@type": "GeoCircle",
"geoMidpoint": {
"@type": "GeoCoordinates",
"latitude": 35.6464,
"longitude": 139.6533
},
"geoRadius": "10000"
},
"providerMobility": "dynamic"
}
geoRadius is in meters (the unit is implicit per the schema.org definition). The geoMidpoint is the dispatch origin. There is no storefront to point at. This is the right shape for any small operator whose actual constraint is drive time and who is not bound to administrative boundaries.
The trap: GeoCircle is structurally clean, but circles do not match how customers think. A customer in a thin sliver of your circle that is across a river with no bridge is not actually in your service area. AI assistants asked “will they come to me?” read the circle literally. They do not know about the river.
Encoding B — AdministrativeArea for political boundaries
When the business serves whole wards, cities, or prefectures, the polygon-shaped truth is “yes if your address is in this jurisdiction, no otherwise.”
{
"@context": "https://schema.org",
"@type": "MovingCompany",
"@id": "https://example.com/#business",
"name": "Kanto Move Co.",
"areaServed": [
{
"@type": "AdministrativeArea",
"name": "Tokyo",
"containedInPlace": { "@type": "Country", "name": "Japan" }
},
{
"@type": "AdministrativeArea",
"name": "Kanagawa",
"containedInPlace": { "@type": "Country", "name": "Japan" }
},
{
"@type": "AdministrativeArea",
"name": "Saitama",
"containedInPlace": { "@type": "Country", "name": "Japan" }
}
],
"providerMobility": "dynamic"
}
This encoding aligns with how GBP’s “Service area” picker actually stores its data on the inside (jurisdictional names with parent containment), which is why it tends to flow cleanly through the Knowledge Graph pipeline I described in Reading Google Business Profile as JSON-LD. The downside is that “Tokyo” is a 23-special-ward city plus several dozen suburbs, so a moving company that really only covers central 23-ku is over-claiming if it just says name: "Tokyo".
Encoding C — Place.containedInPlace for narrower geographies
When the area is smaller than a prefecture but larger than a postcode, Place with containedInPlace gives you a hierarchical answer.
{
"@context": "https://schema.org",
"@type": "HousePainter",
"@id": "https://example.com/#business",
"name": "Yokohama Coat & Color",
"areaServed": {
"@type": "Place",
"name": "Yokohama central wards",
"containedInPlace": [
{ "@type": "AdministrativeArea", "name": "Naka-ku" },
{ "@type": "AdministrativeArea", "name": "Nishi-ku" },
{ "@type": "AdministrativeArea", "name": "Hodogaya-ku" }
]
},
"providerMobility": "dynamic"
}
The containedInPlace array is the truthful answer to “which sub-units of Yokohama do you actually cover?”. It is more work to maintain than a GeoCircle, but it is the only one of the three that survives a follow-up question like “do you cover Naka-ku?” with an unambiguous yes.
providerMobility, hasMap, and the deliberate absence
Two more fields matter. They matter because of what they signal to a downstream parser, not because of what they say literally.
providerMobility takes the values "static" or "dynamic". A static provider waits for customers to arrive at a fixed point. A dynamic provider travels. Setting it to "dynamic" is the explicit, structured way to say “my address, if I have one, is not where customers come.” It is the antidote to the Place-shaped-hole problem above.
hasMap is the field a fixed-location business uses to point at its map listing. A service-area business should typically not set it, or should set it to the operator’s GBP Service Area Business profile if one exists. Setting hasMap to a Google Maps pin of the dispatch office is one of the surest ways to tell a parser “come here” when you mean “do not come here.”
address, in turn, should either be omitted or be present only as a PostalAddress with no streetAddress (locality and region only), to signal that it is a billing or contact address, not a customer-facing one. This is the schema.org-level encoding of the same intent that GBP’s “Hide address” toggle expresses at the UI level.
Putting all of this together for a fully shaped service-area business looks like this:
{
"@context": "https://schema.org",
"@type": "Plumber",
"@id": "https://example.com/#business",
"name": "Setagaya Pipe & Drain",
"url": "https://example.com/",
"telephone": "+81-3-1234-5678",
"address": {
"@type": "PostalAddress",
"addressLocality": "Setagaya",
"addressRegion": "Tokyo",
"addressCountry": "JP"
},
"areaServed": {
"@type": "GeoCircle",
"geoMidpoint": {
"@type": "GeoCoordinates",
"latitude": 35.6464,
"longitude": 139.6533
},
"geoRadius": "10000"
},
"providerMobility": "dynamic",
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "08:00",
"closes": "20:00"
}
]
}
Notice what is not here: geo on the business itself, hasMap, streetAddress. None of those are forbidden. They are just lies for this business, and lying to the parser is how you end up cited as the plumber that does not actually come to the customer asking.
How AI assistants read all this: what we can and cannot say
I want to be careful here. The 4-engine measured citation behavior for service-area queries is not a dataset I have. What I can offer is documented architecture-based inference, not measured citation: a read of what the public engine documentation and the schema.org spec, taken together, imply about how a coverage query gets resolved.
Cross-matrix of what should fall out of those documented architectures:
| Stage in the citation event | Fixed-location query (“cafe near me”) | Service-area query (“plumber who comes to 154-0023”) |
|---|---|---|
| 1. Candidate retrieval | Geo-filter by geo proximity to user | Filter by areaServed containment of user’s location |
| 2. Entity disambiguation | @id + address + name | @id + name + service type + providerMobility |
| 3. Coverage check | Implicit (you are nearby) | Explicit (does the polygon/circle contain me?) |
| 4. Confidence weighting | Reviews, distance, hours | Reviews, coverage match strength, response time |
| 5. Citation surface | ”X is 400m from you" | "X serves your area” |
The asymmetric step is stage 3. For a fixed-location business, “are you near me?” is the same question as “do you exist on the map near me?”. For a service-area business, the two questions are different, and only the structured data answers the second one. If your JSON-LD only encodes address, the parser is left to either reuse the fixed-location stage 3 (almost always wrong: your office is in Setagaya, the customer is in Suginami, so the answer becomes “no”) or to skip stage 3 entirely (almost always wrong in the other direction: the customer in Hokkaido is told you can come).
This is the part where I would like to claim the right areaServed encoding guarantees correct citation. I cannot. AI engines aggregate signals from multiple structured-data fragments, from third-party citation directories, and from review prose, and the structured areaServed is just one input among them. What I can say is the inverse: a Place-shaped JSON-LD for a service-area business gives every downstream parser the wrong shape to start from. Fixing the shape is necessary, not sufficient.
A single test you can run today
Find your own business in the JSON-LD <script> blocks of your homepage (view-source: and search for application/ld+json). For each block representing the business, check three things:
- Is the
@typea service-area-aware subtype (Plumber,HousePainter,MovingCompany,EmergencyService) rather than the bareLocalBusiness? - Is there an
areaServed(orserviceArea), and is it one ofGeoCircle,AdministrativeArea, or aPlace.containedInPlacestructure, rather than free text? - Is
providerMobilityset to"dynamic", and is theaddresseither absent or limited to locality/region withoutstreetAddress?
If any of the three is missing, the AI assistants reading your structured data are guessing at the answer to the most important question a customer can ask you. They guess in your favor sometimes. They guess against you sometimes. The point of writing JSON-LD at all is to stop being guessed at.
The harder thing (getting from “encoding is correct” to “AI assistants cite you for the right queries”) is downstream. It depends on the rest of the LLMO Framework’s Discoverability and Citation components being healthy, on your GBP being in agreement with your site, on review prose not contradicting your areaServed. The encoding is just the first invariant. But it is the one almost no service-area business gets right today, which is the only reason I bothered to write any of this down.