What we found auditing the five sites that rank for Tampa and St. Pete SEO terms
Structured data is the rare technical lever where you can inspect your competitors’ work directly. It sits in the page source. There is nothing to infer and nothing to buy a tool for.
So we looked. We took the queries a Tampa Bay business actually types when it goes looking for help — variations on SEO company Tampa, SEO St. Petersburg, digital marketing agency Tampa Bay — pulled the top five organic results, and read the structured data on each site’s home page and its strongest-ranking service page.
The pattern was consistent enough to write down:
- All five shipped some form of
OrganizationorWebSitemarkup, generally whatever their CMS emits by default. - Four described themselves as a
LocalBusinessor one of its subtypes. Three of those left out fields that are required or strongly recommended for the type they had chosen. - None of the five shipped
FAQPagemarkup, despite four of them having pages built entirely as question-and-answer blocks. - None of the five shipped
RevieworAggregateRatingmarkup, despite every one of them displaying client testimonials. - Two carried an error serious enough that the node failed validation outright, which means the markup was doing nothing whatsoever.
Run the same check yourself before you take our word for it — markup changes, and the point of this article is the method, not the scoreboard. View source on any competitor, search for application/ld+json, and paste what you find into a validator. It takes about ninety seconds per site.
What matters is that the gap is real and it is unusually wide for something this cheap to close.
LocalBusiness schema: the fields most Tampa Bay businesses leave empty
The default LocalBusiness block generated by a theme or plugin is usually a name, a URL and a logo. That is an identity card, not a description of a business.
The fields that carry actual information, and that were routinely missing:
addressas a structuredPostalAddress, not a single string. Street, locality, region, postal code and country as separate properties. A concatenated address is a piece of text; aPostalAddressis data.geowith latitude and longitude. Cheap to add, and it disambiguates you from every similarly named business in the metro.areaServednaming the cities and counties you actually cover. Almost nobody fills this in, and for a service-area business it is the single most useful property on the node.openingHoursSpecificationin the structured form, rather than a line of prose in your footer.sameAspointing to your Google Business Profile, your verified social accounts and any authoritative directory listing. This is how you tell a machine that the entity on your site is the same entity in those other places.hasOfferCatalogormakesOfferlisting your services. Without it, a parser knows you exist but not what you sell.@id— a stable, absolute URL identifier for your business entity, reused everywhere you reference it.
One rule underneath all of it: only mark up what a human can see on the page. Schema describes visible content. Inventing it is a policy violation, and it is the reason a lot of technically valid markup gets ignored.
Why FAQPage markup is still unclaimed in this market
There is an honest reason nobody in this market ships FAQ markup, and it is not laziness. Google narrowed FAQ rich results to a small set of authoritative government and health sites. If you add FAQPage markup to a St. Petersburg law firm’s page hoping for expandable questions under your listing, you will wait forever.
That is the caveat, and it should be stated plainly before anybody spends a morning on this.
The reason to ship it anyway is different. FAQPage markup takes a block of prose and declares, unambiguously, that this string is a question and this specific string is its answer. It turns a wall of copy into labelled, self-contained passages. Systems that quote your page — whether that is an AI answer, an aggregator or an internal search index — work in passages, and a passage that arrives pre-labelled with the question it answers is easier to lift correctly than one that has to be inferred from surrounding text.
It also imposes useful discipline on the writing. An answer that has to stand alone inside a JSON string cannot begin with “as mentioned above” or trail off into a second paragraph of context. That constraint tends to improve the visible page as much as the markup.
So: implement it for structure, not for ornament. If you would remove it when the accordions fail to appear, do not add it in the first place.
Review and AggregateRating: the markup nobody here ships despite having the testimonials
Every site in the audit displayed testimonials. Not one described them in structured data.
Here the caveat is even sharper. Reviews you collect about your own business and publish on your own site are self-serving reviews under Google’s policy, and they are not eligible for review rich results. Marking up your testimonials page will not put stars in the search results, and a good deal of the advice you will find online is quietly wrong about this.
What AggregateRating still does is state your rating and review count in a form no parser has to guess at, and keep that claim consistent with what your Google Business Profile and third-party platforms say. Consistency across sources is the whole game in entity understanding, and a testimonials page that says one thing while your profile says another is a small, unnecessary contradiction.
The hard rules, which matter more than the implementation:
- The rating and count must match what is genuinely visible on the page.
- Never aggregate reviews you cannot display.
- Never mark up a rating you invented, rounded up, or pulled from a platform you no longer use.
Fabricated ratings are a spam violation with real consequences and no upside. If your testimonials are thin, the fix is collecting more of them, which is a conversation about operations rather than code — we covered the mechanics of that in how local SEO actually drives phone calls.
Three real schema bugs we found, and how to check for them on your own site
These are the specific failures from the audit, all of which are checkable in a couple of minutes.
1. JSON-LD injected by JavaScript that never rendered. The markup existed in a script bundle and appeared correctly in the browser inspector, so it looked fine to whoever built it. It did not appear in the rendered HTML the crawler received. Check: use Search Console’s URL Inspection tool, open the rendered HTML, and search for ld+json. If it is absent there, the markup does not exist as far as search is concerned.
2. Two conflicting LocalBusiness nodes on the same page. One from the theme, one from an SEO plugin, with different names and different addresses. A parser given two contradictory descriptions of the same entity has no way to resolve them. Check: view source and count how many application/ld+json blocks the page contains. More than one is fine — more than one describing the same business is not.
3. Relative image URLs inside the markup. /images/logo.png rather than the full absolute URL. Structured data requires absolute URLs, so the property resolves to nothing and the image silently fails to associate. Check: every URL inside your JSON-LD should start with https://. No exceptions.
None of these three produce a visible symptom on the page. That is precisely why they survive for years on sites that are otherwise well maintained, and why a technical SEO pass finds them so reliably.
Multi-location and service-area markup for Pinellas and Hillsborough businesses
If you operate from more than one address, the mistake is stacking every location into the home page markup. The correct structure is one LocalBusiness node per location, on that location’s own page, each with its own @id matching that page’s URL and its own address, phone number, hours and geo coordinates. The pages are what get indexed and what get returned; the markup should live where the content does.
Service-area businesses without a public storefront have a different problem. LocalBusiness expects an address, but a mobile business does not want a home address in search results. The workable approach is to keep a real postal address in the markup where you have one, and lean on areaServed to describe coverage — either as a list of named places, or as a GeoCircle with a centre point and radius in metres.
For a Pinellas or Hillsborough business this is where you can be genuinely specific: name the municipalities you cover rather than writing “Tampa Bay area”. Clearwater, Largo, Seminole and Pinellas Park are distinct places to a parser, and to the people searching from them. The same specificity that makes a St. Petersburg service page worth reading makes the markup underneath it worth parsing.
What schema does and does not do for AI-generated answers
Be careful here, because the claims made about structured data and AI search have outrun the evidence considerably.
What is not established: that adding schema causes an AI system to cite you, or that any of the major answer engines weight structured data as a ranking input. Large language models read rendered text. They do not require JSON-LD to understand a page, and plenty of heavily cited pages carry no markup at all.
What is reasonable: structured data resolves ambiguity cheaply. When a system needs to know which of three similarly named firms in the metro your page describes, where you operate, and what you sell, an explicit @id, a structured address and a populated areaServed answer those questions without inference. Passage-labelled content is easier to quote accurately than undifferentiated prose. And consistency between your markup, your visible page and your third-party profiles is a signal that survives whatever changes at the retrieval layer.
That is a modest, defensible case. It is also enough justification for a day of work — which is roughly what this costs — without needing the inflated version.
A validation routine you can run in fifteen minutes
- Pick three URLs: your home page, your strongest service page, and one location page.
- Run each through the Rich Results Test. This tells you which Google features the page is eligible for. Note errors first, warnings second.
- Run the same three through validator.schema.org. This checks against the full vocabulary and will surface valid properties Google ignores but other consumers use.
- Check the rendered HTML for one of them via URL Inspection, confirming your JSON-LD survives rendering.
- Open Search Console’s structured data reports for the site-wide picture, including the unparsable structured data report, which is where silent syntax failures collect.
- Compare your markup against your Google Business Profile field by field — name, address, phone, hours, categories. Any mismatch is a contradiction you are volunteering.
Most sites finish that list with a short, unglamorous fix list: a missing areaServed, a relative image URL, a duplicate node. None of it is difficult. All of it is sitting unclaimed in a market where the sites currently ranking have not done it either.
If you would rather have someone else run it, our local SEO and technical audits start with exactly this pass, and we will send back the findings whether or not you go further with us. Get in touch and tell us your domain.