
AI is very good at writing a landing page that looks finished: a hero, a feature grid, a stats bar, a row of star ratings and three testimonials with names. The trouble is the numbers. When a model writes a stats bar and nothing tells it the real figures, it does not leave a blank. It writes a plausible number, and a plausible number is the kind nobody checks.
At Careerkit, a resume and career tool, that is exactly what happened on our AI headshots page. It claimed 50,000+ headshots generated and 1,000+ happy professionals. Its structured data told Google the product had 4.8 stars from 2,500 reviews. When we checked on November 5, production held 440 images from 12 people, and no rating system for headshots had ever existed.
Below: what the page said against what the database said, why it matters more than it looks, how a routine refresh found it, the claims file and the code module that now gate every number, how we fixed live blog content without trusting the agent, the miss that let a banned claim survive for seven weeks, and a checklist to audit your own page this week. One caveat up front: this is a builder's write-up, not legal advice.
What the page said, and what the database said
Here is the full list, claim by claim, against what production actually held:
- "50,000+ headshots generated." Production held 440 images.
- "1,000+ happy professionals." Twelve people had used the feature, the last one in July 2026.
- "4.6/5 average rating." No rating system for headshots exists.
- "$500k+ saved vs traditional." No source at all.
- 4.8 stars from 2,500 reviews, sent to Google as aggregateRating markup and invisible on the page. There were no reviews.
- Three named testimonials, one promising a 300% rise in profile views. No source for any of them.
- "Join the 100,000+ job seekers" and similar lines in 179 blog sentences. The real number of people who have used Careerkit is 20,996.
The headshot stats arrived in a large translations commit on January 24, and the review markup came the next day, inside a commit titled "SEO improvements". The copy was AI-written, and nobody re-read the stats bar afterwards, because a stats bar always looks right.
One claim did survive: "50+ headshots per shoot". Every shoot since August 2025 delivered 52 to 56. So the point of a check is not to assume everything is false. It is to know which claims are true and which are not.
Why it matters more than it looks
A made-up stats bar feels like harmless polish. It is not, for three reasons.
- Search engines read what visitors cannot. The aggregateRating markup was invisible on the page, so no visitor could have spotted it. Google's structured data guidelines require markup to describe content that is actually on the page, and sites that break them can get a manual action and lose rich results. We did not know the markup existed until the check found it.
- Fake reviews are regulated. In the US, the FTC's 2024 rule on consumer reviews and testimonials bans fake ones, including AI-generated ones, and allows civil penalties. The EU has listed fake consumer reviews as a banned practice since 2022. A testimonial with no source is a liability, not social proof.
- It poisons your own numbers. If your site says 100,000 and your database says 20,000, every pitch, partner email and AI answer that quotes your site repeats the wrong figure. Assistants read your about page and your llms.txt too.
How a routine refresh found it
Nobody set out to audit the site. The numbers on our pages were a snapshot from October 15, and on November 5 an agent session re-read every product number from the production database, using read-only queries. The refresh covered every number on every page, which is how it reached the headshots page. Its commit message said it plainly:
"Production holds 440 images from 12 people, the last in July 2026, and no rating system for headshots exists."
The stats bar, the hero chip and the review markup came out in that commit. The three testimonials and the "Join thousands" lines came out in the next one, the same day. Deleting them was not a hard call: twelve people are not thousands.
The lesson is about cadence. Our rule now is that any number older than 60 days is re-read from production before anyone quotes it. A boring, scheduled refresh found what months of looking at the page never did.
Step 1: Keep a claims file every number must pass
The fix that matters most is a single file. Ours is called claims.md, and it opens with the rule:
"Every number, customer name, logo, superlative and proof point in anything we ship must trace to a row below. Not here? Write [NEED: <what>] and flag it to the founder. Never invent a plausible figure."
Each row holds a metric, its value, the date it was read and a safe way to say it. Today "over 20,000 people have used Careerkit" is allowed, because the count is 20,996. "Over 20,000 built a resume" is not, because fewer people have. The file also keeps a banned list, with the reason for each entry: "100,000+ job seekers", which was a 6x overstatement against 16,066 users when it was banned, and "trusted by" next to a row of company logos, because one of those logos stood for a single account.
The [NEED] rule is what changes the outcome. A model asked to write a stats bar with no data will fill it. A model told that every number must come from one file, and that a missing number is written as [NEED], leaves a visible gap for a person to fill. A blank it has to flag beats a number it made up.
Step 2: Give the code one source of numbers
The claims file governs copy. For the site itself, pages read their numbers from one module, product-stats.ts, which records the date the figures were read from production. Its header states the rule in one line: a page may show a number smaller than the truth, never larger.
Two details make it hold up. First, the module lists every string that bypasses it (the about pages, the sign-in panel and one FAQ answer), so the next refresh knows where else to look. Second, each count says exactly what it measures. Our user count is signed-up accounts plus guests who made a document; the 1,308 guest sessions that made nothing stay out. Vague definitions are where inflation hides, so write the definition next to the number.
Step 3: Fix live content without trusting the agent
The headshots page lived in the code, so its fix went through a normal commit. The blog did not. Our posts live in the database, in English, German and French, so changing 179 sentences meant writing to live content, which is exactly where you do not want an agent improvising. The fix ran in four stages:
- Dry run first. The session produced a plan file listing every sentence it would change, before and after. A person read it.
- A script that refuses to freelance. The fix ran in one short transaction. If anything differed from the reviewed plan, or a row had changed since it was read, it aborted and wrote nothing. It touched the post body only (no dates, slugs or status) and backed everything up first.
- A human approves the write. The first attempt was stopped by the session's own safety check before it reached the live blog. It came back as a single command for the founder.
- Round down. Every corrected sentence now says 20,000, below the real 20,996, never above it.
Each fix commit also named a guardrail metric that must not drop afterwards, such as Search Console clicks on the headshots page staying flat. If removing fake proof costs real traffic, you want to see it, not guess.
The miss: a ban is not a cleanup
The blog fix was actually the second pass. On September 13 we banned "100,000+ job seekers" in the claims file, removed it from every page and moved on. Every page in the code, that is. The blog lives in the database, so nothing looked there, and 179 sentences kept saying it for seven more weeks.
A ban stops new copy. It does not clean old copy. When you retire a claim, search every place your words actually live: the codebase, the CMS or blog database, email templates, app store listings, directory profiles and your social bios.
How to audit your own page this week
- List every claim. Open your landing page, pricing page, about page and one popular blog post. Write down every number, star rating, logo, testimonial and superlative.
- Check what search engines see. View the page source and search for aggregateRating, reviewCount and ratingValue, or paste the URL into Google's Rich Results Test. This is where invisible claims hide.
- Ask for the query. For each claim, ask your agent for the read-only database query that proves it, then run it. A prompt that works: "List every number, rating, logo, testimonial and superlative in these files. For each, give the read-only SQL that proves it, or mark it [NEED]."
- Delete or shrink. No query, no claim. Delete it, or replace it with the real number even when the real number is smaller.
- Write the survivors down. Put them in one claims file with the date each was read, and tell your agent that no number ships unless it comes from that file.
- Search everywhere. Grep the repo and query the CMS for every claim you removed, so the old version does not live on somewhere else.
- Put a date on it. Re-read every number from production before it turns 60 days old.
Practical takeaways
- AI fills empty stats bars with plausible numbers. Treat an AI-written number as invented until a query proves it.
- Structured data is part of your claims. Check aggregateRating as carefully as the visible copy.
- One claims file, one stats module and a [NEED] rule turn invented numbers into visible gaps.
- Bulk edits to live content go dry run, human review, then a script that aborts if reality changed.
- Retiring a claim means searching every place your copy lives, not just the codebase.
- Smaller and true beats big and made up. Round down.
Conclusion
The page did not lie on purpose. A model filled in a template in January, and nobody checked the template against the database until November. The fix was not a rewrite. It was a file of allowed numbers, a refresh on a schedule and the habit of asking for the query behind every claim. Every number on your page should match a query you can run today. If you cannot write the query, the number is not yours.
The founder-side version of this story ran in the HelloBuilder newsletter. For the same lesson applied to deploys instead of copy, read An Agent Said It Deployed. The Containers Were a Day Old.


