# Self-serving stars, 9am hours and 555 phones: local schema Google throws away

Stars on your own LocalBusiness node, opens set to 9am and a 555 phone: three local schema errors Google discards, what the Mega Analyzer reads, and how to fix each one.

Author: J.A. Watte
Published: September 21, 2026
Source: https://jwatte.com/blog/blog-mega-analyzer-local-schema-validity/

---

When the [Mega Analyzer](/tools/mega-analyzer/) prints **Self-serving review markup on your own LocalBusiness / Organization node (Motel (4.7))**, **openingHoursSpecification malformed: opens "9am" (needs HH:MM)** or **Placeholder contact data in JSON-LD: telephone 555-0100** on the Structured Data card, it is reporting three ways a local business JSON-LD block can be valid JSON, valid schema.org, and still say nothing Google will use. The pass rows are **No self-serving review markup on the business node**, **openingHoursSpecification well-formed (N entries)** and **No placeholder contact data in JSON-LD**. All three come from one walk over the page's JSON-LD, and all three are errors a validator that only checks syntax waves through.

## What the check actually tests

The analyzer reads every `<script type="application/ld+json">` block in the served HTML, parses each one, and recurses into every object value: arrays, `@graph`, a `review` nested in a `Product`, an `itemOffered` inside an `OfferCatalog`. Every object with an `@type` becomes a node. A block that fails to parse is counted on the separate JSON-LD errors row and never reaches these checks. Microdata and RDFa are not read at all, so hours written as `itemprop="openingHours"` in the HTML are invisible to all three rows.

A node counts as a business node when any of its types is in the analyzer's LocalBusiness family (130 named types, `Motel` and `TireShop` among them, plus a suffix match for names ending in Business, Service, Store, Salon, Spa, Restaurant, Hotel, Motel, Shop, Agency, Center, Office and a few more) or is one of `Organization`, `Corporation`, `MedicalOrganization`, `NGO`, `EducationalOrganization` or `LocalBusiness` itself. The named list includes plain `Service`, which matters below.

**Self-serving reviews.** The row renders only when at least one business node exists. It fails when any business node carries an `aggregateRating`, `review` or `reviews` property with any non-empty value. The fail text names the node's first type and, when there is one, the `ratingValue` trimmed to five characters, up to four nodes: `Motel (4.7)`. Note what it does not do. It does not decide whether the node is your business or someone else's; a directory page that marks up twenty plumbers with their ratings fails this row too. And it does not fire on a rating that sits on a `Product`, `Recipe` or `Review` node, or on testimonials rendered as plain HTML. A `Service` node is in the family, so a rating there fails, even though the row's own advice text mentions Services as a home for review markup.

**Opening hours.** This one walks every node, business or not, and counts entries. Each `openingHoursSpecification` item (single object or array) is one entry, and so is each `openingHours` string. An item that is not an object is reported as "specification is not an object". Every `dayOfWeek` value must be exactly `Monday` through `Sunday` or `PublicHolidays`, optionally prefixed with `http://schema.org/` or `https://schema.org/`; the match is case-sensitive, so `monday`, `Mon`, `Mo` and `Mon-Fri` all fail. `opens` and `closes` must match `HH:MM` with an optional `:SS`, hours `00` through `24`, so `09:00`, `09:00:00`, `23:59` and `24:00` pass while `9:00`, `9am`, `09.00` and `9:00 AM` fail with "(needs HH:MM)". An item with `opens` but no `closes`, or the reverse, is reported as "opens without closes". An item with no `dayOfWeek` at all is accepted, because a seasonal entry keyed on `validFrom` and `validThrough` is a legitimate schema.org shape. The string form is checked separately: each `openingHours` value must be two-letter days (`Mo` to `Su`), optional hyphen ranges, optional comma lists, and at most one `HH:MM-HH:MM` time range, so `Mo-Fr 09:00-17:00` and `Mo-Su` pass while `Monday-Friday 9am-5pm`, `Mo-Fr 9:00-17:00` and `Mo-Fr 09:00-12:00,13:00-17:00` (two ranges in one string) fail. The row renders only when the count is above zero and lists up to six distinct problems.

**Placeholder contact data.** Also every node. A `telephone` fails when it contains `555`, then an optional hyphen, then `01` and two more digits: `555-0100`, `5550100` and `+12085550100` all trip it, while a space-separated `555 0100` slips past. That is the 555-0100 through 555-0199 block the North American Numbering Plan Administrator keeps as "fictitious, non-working numbers" reserved for entertainment and advertising. It also fails when all ten digits are the same (with or without a leading 1), when the digits contain `1234567890` or `0000000000`, or when the raw value has four or more `X` characters in a row. `email` and `url` fail on `example.com`, `example.org`, `example.net`, `yourdomain`, `yourbusiness`, `yoursite` or `placeholder` anywhere in the value. `name` and `legalName` fail only on an exact, case-insensitive match of `Your Business`, `Your Company`, `Your Business Name`, `Your Company Name`, `Company Name`, `Business Name` or `Lorem Ipsum`. Each `sameAs` fails on `example.com`, `yourpage`, `yourhandle` or a `/username` path segment. This row renders when a business node exists or when at least one placeholder was found anywhere, so a `WebSite` node with `"url": "https://www.example.com/"` produces the fail row even on a page with no business markup. It does not fire on `555-1234` (outside the 01xx block), on a name like `Example Bakery`, on `example.co`, or on a placeholder in the visible footer; the **Phone numbers disagree** row catches that, and only when the JSON-LD number differs from the one shown.

The three rows are weighted 4, 3 and 3 in the schema score. A row that does not apply costs nothing.

## Why it matters

Google's review snippet documentation is direct about the first one: "If the entity that's being reviewed controls the reviews about itself, their pages that use LocalBusiness or any other type of Organization structured data are ineligible for star review feature." The same page says "Ratings must be sourced directly from users" and warns not to "include fake or undisclosed incentivized reviews on your page or in your structured data markup," with the consequence that "Google may take manual action against it." The LocalBusiness reference lists `aggregateRating` and `review` as recommended properties with the caveat that they are "only recommended for sites that capture reviews about other local businesses." That has been the rule since Google's September 2019 change to review rich results. A motel site I audited this month had `aggregateRating` 4.7 from 212 reviews on its `Motel` node, typed into a theme's SEO panel from the Google Business Profile. Google's own panel for the business already showed those stars, read from the profile. The copy in the JSON-LD earned nothing: self-serving review markup on a LocalBusiness node is exactly what the September 2019 change stopped displaying.

The hours row is about a format Google publishes and a value the page did not follow. The LocalBusiness reference defines `opens` as "the time the business location opens, in hh:mm:ss format" and `dayOfWeek` as "one or more of the following values: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday." Schema.org's own `openingHours` note says "Times are specified using 24:00 format. For example, 3pm is specified as 15:00." A value of `9am` is not a time in either definition. Nothing downstream can compare it with the clock, so the hours the page offered contribute nothing to what Search or an assistant shows, and the "open now" answer, if there is one, comes from somewhere else or not at all. When the Google Business Profile has drifted, the JSON-LD was the one place the site could have stated its hours unambiguously, and it wasted the chance.

Placeholders are the cheapest to fix and the most embarrassing to leave. The structured data guidelines say "Your structured data must be a true representation of the page content." A `telephone` of `555-0100` is a true representation of nothing; RFC 2606 reserves `example.com` for documentation, and the 555-01xx block exists so that a number in a script never rings a real phone. The JSON-LD `telephone` is the one machine-readable field whose whole purpose is to answer "what is this business's phone number," so a crawler or an assistant that reads it has no reason to distrust it. The usual origin is a theme that ships demo JSON-LD: the owner fills the footer with the real number and never opens the theme's schema panel, so the visible page is right, the JSON-LD is wrong, and the **Phone numbers disagree** row flags the mismatch on top.

## How to fix it

**Step 1: get the full list.** Run the domain through the [Local Schema Validity Audit](/tools/local-schema-validity-audit/). It applies the same three rules with more context: it decides whether a rated business node is your brand or a third party by comparing the node's `url` and `@id` host, its `name`, and the site's `og:site_name`, and it has a field where you type the brand name if the node is named differently. It also checks address completeness, `geo` sanity and whether the JSON-LD phone matches the visible one, none of which the Mega Analyzer row covers.

**Step 2: take the rating off the business node.** This is the block that fails all three rows at once, in the shape a template ships:

```json
{
  "@context": "https://schema.org",
  "@type": "Motel",
  "name": "Your Business",
  "url": "https://www.example.com/",
  "telephone": "555-0100",
  "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.7", "reviewCount": "212" },
  "openingHoursSpecification": [
    { "@type": "OpeningHoursSpecification", "dayOfWeek": "Mon-Fri", "opens": "9am", "closes": "5pm" }
  ]
}
```

Delete `aggregateRating`, `review` and `reviews` from the `Motel` node. Keep the testimonials on the page as ordinary HTML; Google's rule is about markup, not about showing reviews. Add the Google Business Profile share link to `sameAs` so the profile carries the business reviews, which is where the panel already reads them from. If you truly have reviews about a specific item collected from customers on that page, the markup belongs on that item's node, for example a `Product` with the rating and a `brand` pointing at the business `@id`. Do not reach for `Service` as the escape hatch: the analyzer counts a `Service` node as part of the business family, so the row fails there too, and Google's review snippet page does not list `Service` among the types that get stars anyway. For most local businesses the right move is removal.

**Step 3: rewrite the hours.** Use full day names, one entry per group of days that share the same hours, and 24-hour `HH:MM`. Google's conventions cover the edge cases: a day the business is closed is `"opens": "00:00", "closes": "00:00"`; open around the clock is `"opens": "00:00", "closes": "23:59"`; a shift that runs past midnight stays in one entry with the closing time on the next day, such as `"opens": "18:00", "closes": "03:00"` on Saturday. This is the corrected node in the shape the [Local Schema Validity Audit](/tools/local-schema-validity-audit/) emits, with `FILL` markers where the value has to be yours:

```json
{
  "@context": "https://schema.org",
  "@type": "Motel",
  "@id": "https://sagebrushcourt.example/#localbusiness",
  "name": "Sagebrush Court Motel",
  "url": "https://sagebrushcourt.example/",
  "telephone": "<<FILL: +1 then area code and number, digits only, E.164 form>>",
  "image": "<<FILL: absolute URL of a photo of the building>>",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "<<FILL: street number and name>>",
    "addressLocality": "<<FILL: city>>",
    "addressRegion": "ID",
    "postalCode": "<<FILL: postal code>>",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": "<<FILL: decimal latitude from the map pin>>",
    "longitude": "<<FILL: decimal longitude, negative in the Americas>>"
  },
  "openingHoursSpecification": [
    { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "opens": "09:00", "closes": "17:00" },
    { "@type": "OpeningHoursSpecification", "dayOfWeek": "Saturday", "opens": "10:00", "closes": "14:00" },
    { "@type": "OpeningHoursSpecification", "dayOfWeek": "Sunday", "opens": "00:00", "closes": "00:00" }
  ],
  "sameAs": ["<<FILL: Google Business Profile share link>>"]
}
```

If the template used the string form, either convert it or make it match: `"openingHours": ["Mo-Fr 09:00-17:00", "Sa 10:00-14:00"]`. Two shifts on one day go in two strings, not one. Google documents `openingHoursSpecification` for LocalBusiness and the string form carries less detail, so convert when you can.

**Step 4: replace every template value.** Grep the theme or template source, not the rendered page, for `555-01`, `example.com`, `Your Business`, `yourdomain` and `XXX`. Fix the field the value comes from (a theme options panel, a CMS SEO plugin, a hard-coded partial) so it cannot come back on the next deploy. Write the phone with the country code; Google's LocalBusiness page says to "be sure to include the country code and area code," and the `+1` E.164 form is unambiguous for every parser.

**Step 5: regenerate and verify.** If the node is a mess, paste the URL into the [Schema Fix Bundle](/tools/schema-fix-bundle/) and take its LocalBusiness block as the starting point rather than patching the template's. Check the result in the [Schema Validator](/tools/schema-validator/), which knows Google's per-type required and recommended properties, then in Google's Rich Results Test, then re-run the Mega Analyzer and confirm all three rows are green.

## When to leave it alone

The self-serving row has one honest exception the Mega Analyzer cannot see: a page that reviews other businesses. Google's LocalBusiness reference recommends `aggregateRating` and `review` precisely "for sites that capture reviews about other local businesses," so a local directory, a "best of" roundup or a comparison page is doing exactly what the documentation describes when it puts ratings on someone else's `LocalBusiness` node. The Mega Analyzer row will still fail, because it does not compare the node to your brand. Run the [Local Schema Validity Audit](/tools/local-schema-validity-audit/) instead; it downgrades a rated node that does not match the site brand to a warning and tells you which signal it matched on. Make sure each reviewed node names the business and links to it, and keep your own site's `Organization` node clean.

Hours have two. A business with no hours in any real sense (a service that goes to the customer, an online-only operation) should not carry `openingHoursSpecification` at all, and without it the row never renders. And a seasonal entry that carries only `validFrom` and `validThrough` with no `dayOfWeek` passes as written; that is a schema.org shape, not a defect. One quirk to know about: the analyzer accepts a single time range per `openingHours` string, so a legitimate split shift written as one string fails even though nothing on schema.org forbids it. Split it into two strings and move on. Also remember what the markup does not do. Google's LocalBusiness documentation never mentions Google Business Profile, and nothing in it says the markup feeds the hours shown on Maps or in the panel; the profile is edited on its own. Fix the profile first and use the markup to agree with it.

Placeholders have no exception on a production site. They do have a place: a staging deploy should carry them until launch, and the analyzer pointed at staging is right to complain. The fix there is a launch gate that fails the deploy on `555-01`, `example.com` and `Your Business` in the built HTML, not an allowlist that teaches the analyzer to ignore them.

## Fact-check notes and sources

- **Source**: https://developers.google.com/search/docs/appearance/structured-data/review-snippet establishes "If the entity that's being reviewed controls the reviews about itself, their pages that use LocalBusiness or any other type of Organization structured data are ineligible for star review feature," "Ratings must be sourced directly from users," the instruction not to "include fake or undisclosed incentivized reviews on your page or in your structured data markup," and that violations mean "Google may take manual action against it." Its eligible-type list does not include Service.
- **Source**: https://developers.google.com/search/blog/2019/09/making-review-rich-results-more-helpful establishes the September 2019 announcement that self-serving reviews on LocalBusiness and Organization would no longer be displayed and that review rich results were limited to a defined set of types.
- **Source**: https://developers.google.com/search/docs/appearance/structured-data/local-business establishes `opens` and `closes` "in hh:mm:ss format," `dayOfWeek` as "one or more of the following values: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday," the closed-day convention of `opens` and `closes` both `00:00`, the 24-hour convention of `00:00` to `23:59`, the single-entry rule for hours past midnight, that `aggregateRating` and `review` are "only recommended for sites that capture reviews about other local businesses," and the telephone guidance to "include the country code and area code."
- **Source**: https://schema.org/OpeningHoursSpecification establishes that `opens` and `closes` expect a `Time`, `dayOfWeek` expects a `DayOfWeek`, and `validFrom` and `validThrough` expect a `Date` or `DateTime`; its example includes entries with no `dayOfWeek`.
- **Source**: https://schema.org/openingHours establishes the two-letter day codes `Mo, Tu, We, Th, Fr, Sa, Su` and that "Times are specified using 24:00 format. For example, 3pm is specified as 15:00."
- **Source**: https://developers.google.com/search/docs/appearance/structured-data/sd-policies establishes "Don't mark up irrelevant or misleading content, such as fake reviews," "Your structured data must be a true representation of the page content," and that a structured data issue "can result in a manual action."
- **Source**: https://developers.google.com/search/docs/essentials/spam-policies establishes that "Sites that violate our policies may rank lower in results or not appear in results at all."
- **Source**: https://datatracker.ietf.org/doc/html/rfc2606 establishes that `example.com`, `example.net` and `example.org` are reserved by IANA for use as examples.
- **Source**: https://www.nanpa.com/numbering/555-line-numbers establishes that "the fictitious, non-working numbers, 555-0100 through 555-0199, will remain reserved for entertainment/advertising."
- The type list, the day and time regular expressions, the placeholder patterns and the 4/3/3 weights come from the Mega Analyzer source at https://jwatte.com/tools/mega-analyzer/ (view source; search for `selfServing`, `hoursProblems` and `placeholderNap`).

## Related reading

- [Your reviews exist but Google cannot see them](/blog/blog-tool-social-proof-schema/)
- [Unfinished-site tells: hello-world slugs in your sitemap, lorem ipsum in production](/blog/blog-mega-analyzer-placeholder-leftovers/)
- [NAP consistency for local rank: why Street vs St. costs you map-pack position](/blog/blog-tool-nap-consistency/)
- [A Schema.org Validator That Knows Google Rich-Results Requirements Per Type](/blog/blog-tool-schema-validator/)

Most of these three errors arrive with a theme, on the day a solo owner launches a site from a template and fills in the parts that are visible. *The $97 Launch* spends a chapter on the parts that are not, and the JSON-LD block is the first one to check.

*This post is informational, not legal advice. Mentions of third parties are nominative fair use. No affiliation is implied.*


---

Canonical HTML: https://jwatte.com/blog/blog-mega-analyzer-local-schema-validity/
RSS: https://jwatte.com/feed.xml
JSON Feed: https://jwatte.com/feed.json
Hero image: https://jwatte.com/images/blog-mega-analyzer-local-schema-validity.webp
