E·A

E-E-A-T Auditor

Page-level forensic audit · Experience, Expertise, Authoritativeness, Trust

01Source HTML

Paste the full HTML of a page (View Source > Select All > Copy). Tool runs entirely client-side.
This tool does not fetch URLs. Paste source HTML here. To get a page's source, open it in a browser, right-click, "View page source", then Select All and Copy.
0

Awaiting input

Paste page HTML and click "Run audit".

0 Checks passed
Audit results will appear here.

Why BYOK SaaS Beats Bundled Pricing

By , published , updated

BYOK pricing saves users 60 to 90 percent on AI tool subscriptions, according to our 2026 pricing study of 240 indie SaaS buyers.

For more on methodology, see the full methodology or contact the author.

`, weak: ` The Best AI Tools for 2026

The Best AI Tools for 2026

In today's fast-paced world, AI tools are revolutionary. There are many great options to choose from. We've leveraged our expertise to bring you the best picks.

It's important to find tools that work for you. Don't forget what we covered earlier.

`, clear: '' }; const CHECKS = { experience: { letter: 'E', label: 'Experience', sub: 'First-person evidence the author has done the thing', checks: [ { id: 'first-person', label: 'First-person language present', pts: 5, fn: (doc, text) => { const fp = (text.match(/\b(I |we |our |my |us )/gi) || []).length; if (fp >= 5) return { pass: 'pass', detail: `${fp} first-person markers found.` }; if (fp >= 1) return { pass: 'warn', detail: `${fp} first-person markers. More signals lived experience.` }; return { pass: 'fail', detail: 'No first-person language. Pages with zero "I" or "we" score lower for Experience.' }; }}, { id: 'concrete-numbers', label: 'Concrete numbers in body', pts: 5, fn: (doc, text) => { const nums = (text.match(/\b\d+(?:[.,]\d+)?(%|x| percent| million| billion| years?| months?| days?)?\b/gi) || []).length; if (nums >= 5) return { pass: 'pass', detail: `${nums} numeric facts in body.` }; if (nums >= 2) return { pass: 'warn', detail: `${nums} numeric facts. More raises Experience signal.` }; return { pass: 'fail', detail: 'Very few specific numbers. Experience signals come from concrete data.' }; }}, { id: 'case-study', label: 'Case study or example language', pts: 4, fn: (doc, text) => { const hits = (text.match(/\b(for example|case study|in practice|when we|here's how|the time I|last (year|month|quarter)|in (20\d{2}))/gi) || []).length; if (hits >= 2) return { pass: 'pass', detail: `${hits} concrete-example phrases found.` }; if (hits === 1) return { pass: 'warn', detail: 'One example phrase. Add more to show lived experience.' }; return { pass: 'fail', detail: 'No example or case-study phrases. Reads as abstract.' }; }} ] }, expertise: { letter: 'E', label: 'Expertise', sub: 'Credentials and depth markers', checks: [ { id: 'author-byline', label: 'Author byline present', pts: 6, fn: (doc) => { const byline = doc.querySelector('[rel="author"], .byline, .author, [class*="author-name"], [itemprop="author"]'); if (byline) return { pass: 'pass', detail: 'Author byline element found.' }; const meta = doc.querySelector('meta[name="author"]'); if (meta && meta.getAttribute('content')) return { pass: 'warn', detail: `Only meta author tag: "${meta.getAttribute('content')}". Add a visible byline.` }; return { pass: 'fail', detail: 'No author byline. Anonymous content scores low for Expertise.' }; }}, { id: 'author-bio', label: 'Author bio section', pts: 4, fn: (doc, text) => { const bio = doc.querySelector('.author-bio, .bio, [class*="about-author"], [class*="author-info"]'); if (bio) return { pass: 'pass', detail: 'Author bio section found.' }; const aboutLink = doc.querySelector('a[href*="/author"], a[href*="/about"]'); if (aboutLink) return { pass: 'warn', detail: 'Link to author/about page found, but no inline bio.' }; return { pass: 'fail', detail: 'No author bio section or author page link.' }; }}, { id: 'word-count', label: 'Substantial content (>500 words)', pts: 4, fn: (doc, text) => { const wc = text.split(/\s+/).filter(Boolean).length; if (wc >= 1500) return { pass: 'pass', detail: `${wc} words. Deep coverage.` }; if (wc >= 500) return { pass: 'pass', detail: `${wc} words. Adequate depth.` }; if (wc >= 200) return { pass: 'warn', detail: `${wc} words. Thin for an expertise-claim page.` }; return { pass: 'fail', detail: `${wc} words. Too thin to demonstrate expertise.` }; }} ] }, authoritativeness: { letter: 'A', label: 'Authoritativeness', sub: 'Schema, citations, and outbound links', checks: [ { id: 'person-schema', label: 'Person or Article schema present', pts: 7, fn: (doc) => { const scripts = doc.querySelectorAll('script[type="application/ld+json"]'); let hasPerson = false, hasArticle = false; scripts.forEach(s => { try { const j = JSON.parse(s.textContent); const types = Array.isArray(j) ? j.map(x => x['@type']) : [j['@type']]; if (types.includes('Person')) hasPerson = true; if (types.some(t => ['Article', 'NewsArticle', 'BlogPosting'].includes(t))) { hasArticle = true; const author = Array.isArray(j) ? j.find(x => x.author)?.author : j.author; if (author && (author['@type'] === 'Person' || author.name)) hasPerson = true; } } catch(e) {} }); if (hasArticle && hasPerson) return { pass: 'pass', detail: 'Article schema with Person author found.' }; if (hasArticle) return { pass: 'warn', detail: 'Article schema found but no Person author inside it.' }; if (hasPerson) return { pass: 'warn', detail: 'Person schema found but no Article schema.' }; return { pass: 'fail', detail: 'No Person or Article schema. Both raise Authoritativeness signals.' }; }}, { id: 'sameAs', label: 'sameAs profile links', pts: 4, fn: (doc) => { const scripts = doc.querySelectorAll('script[type="application/ld+json"]'); let total = 0; scripts.forEach(s => { try { const j = JSON.parse(s.textContent); const items = Array.isArray(j) ? j : [j]; items.forEach(item => { const sa = item.sameAs || (item.author && item.author.sameAs); if (Array.isArray(sa)) total += sa.length; }); } catch(e) {} }); if (total >= 3) return { pass: 'pass', detail: `${total} sameAs profile links.` }; if (total >= 1) return { pass: 'warn', detail: `${total} sameAs link(s). Aim for 3+: GitHub, LinkedIn, Twitter, personal site.` }; return { pass: 'fail', detail: 'No sameAs profile links. These connect the author to their public identity.' }; }}, { id: 'outbound-cite', label: 'Outbound citation links', pts: 4, fn: (doc) => { const links = doc.querySelectorAll('a[href^="http"]'); const external = Array.from(links).filter(a => { const href = a.getAttribute('href'); return href && !href.includes('javascript:') && !href.includes('mailto:'); }); if (external.length >= 3) return { pass: 'pass', detail: `${external.length} outbound links found.` }; if (external.length >= 1) return { pass: 'warn', detail: `${external.length} outbound link(s). Authoritative pages cite sources.` }; return { pass: 'fail', detail: 'No outbound citation links. Pages with zero outbound links signal closed-loop content.' }; }} ] }, trust: { letter: 'T', label: 'Trust', sub: 'Dates, contact info, and transparency', checks: [ { id: 'date-published', label: 'datePublished or visible publish date', pts: 6, fn: (doc) => { const time = doc.querySelector('time[datetime]'); const scripts = doc.querySelectorAll('script[type="application/ld+json"]'); let schemaDate = null; scripts.forEach(s => { try { const j = JSON.parse(s.textContent); const items = Array.isArray(j) ? j : [j]; items.forEach(item => { if (item.datePublished) schemaDate = item.datePublished; }); } catch(e) {} }); if (time && schemaDate) return { pass: 'pass', detail: `Visible