Concepts
Reference for the data model, scoring, and classification logic used throughout the Leto SEO API and dashboard.
Health score
Every site receives a composite health score from 0 to 100 computed once per day (05:30 UTC). The score is a weighted sum of component scores, each normalized to the same 0–100 scale before combining. The weights differ by site maturity — early-stage sites emphasize crawlability and impressions; established sites weight traffic and conversions more heavily.
| Range | Label |
|---|---|
| 80–100 | Healthy |
| 50–79 | Needs Attention |
| 0–49 | Critical |
Component weights
Early stage(≤100 organic sessions/mo)
| indexing_progress | 30% |
| keyword_impression_trend | 30% |
| lighthouse_cwv | 25% |
| engagement_quality | 15% |
Established(>100 organic sessions/mo)
| traffic_trend | 30% |
| ranking_trend | 25% |
| cwv_status | 25% |
| conversion_trend | 20% |
Source: src/lib/constants.ts → HEALTH_WEIGHTS, HEALTH_SCORE_RANGES
Core Web Vitals thresholds
Leto uses Google’s official CWV thresholds. Metrics are collected via PageSpeed Insights (lab data, weekly Monday scan). TBT (Total Blocking Time) is the lab proxy for INP (Interaction to Next Paint); both are tracked where available.
| Metric | Label | Pass (good) | Warning | Fail (poor) |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | ≤2500ms | 2501–4000ms | >4000ms |
| INP | Interaction to Next Paint | ≤200ms | 201–500ms | >500ms |
| TBT | Total Blocking Time (lab INP proxy) | ≤200ms | 201–600ms | >600ms |
| CLS | Cumulative Layout Shift | ≤0.1 | 0.11–0.25 | >0.25 |
Alert thresholds used by the CWV alert rule (cwv_fail) are slightly tighter — a page is flagged when any of the following lab values are exceeded:
- LCP > 2500ms
- CLS > 0.1
- TBT > 200ms
Source: src/lib/constants.ts → CWV_THRESHOLDS, ALERT_THRESHOLDS.cwv_fail_threshold
Alert types and severities
Alerts are evaluated once per day (05:45 UTC cron). Duplicate alerts of the same type for the same site within 24 hours are suppressed. Alerts follow a active → acknowledged → resolved lifecycle.
traffic_dropmin baseline: 50 sessions/dayCompares yesterday’s organic sessions to the 7-day rolling average. Sites below the minimum baseline are skipped.
- Warning — drop ≥ 10% vs 7-day avg
- Critical — drop ≥ 30% vs 7-day avg
cwv_failCounts pages where any PSI lab metric exceeds the alert threshold (LCP > 2500ms, CLS > 0.1, TBT > 200ms).
- Warning — any page exceeds any threshold
index_regressionDetects sudden drops in indexed page count as reported by Google Search Console. Compares current indexed count to the previous measurement.
- Critical — indexed pages dropped ≥ 50%
campaign_underperformanceChecks active campaigns that have been running for more than 7 days. Compares GA4 session data ingested via UTM attribution.
- Info — 0 GA4 sessions after 7 days (check UTM parameters)
- Warning — sessions dropped >50% vs first-3-day baseline (min 10 sessions baseline)
Source: src/lib/alerts.ts, src/lib/constants.ts → ALERT_THRESHOLDS
Maturity classification
Every site is classified as either early or established based on its 30-day organic session count from GA4. The threshold is 100 organic sessions per month.
| Tier | Condition | Dashboard focus |
|---|---|---|
| Early | ≤100 organic sessions/mo | Indexing progress, GSC impressions, avg position, Lighthouse score, engagement rate |
| Established | >100 organic sessions/mo | Organic sessions, avg position, CWV score, conversions, health score |
Maturity affects which health score components are used (see Health score) and which KPI strip is shown in the per-site dashboard. The classification is re-evaluated on every daily health-score compute.
The API surfaces maturity in GET /api/v1/sites/:siteId as a maturity field with values "early" or "established". The orb_portfolio_health MCP tool accepts a filter_maturity parameter to restrict results to one tier.
Source: src/lib/constants.ts → MATURITY_THRESHOLD, HEALTH_WEIGHTS, KPI_CONFIG
Time-range semantics
The API accepts a period query parameter (or MCP argument) in several endpoints. Supported values are 7d, 28d, and 90d.
| Value | Meaning | Typical use |
|---|---|---|
| 7d | Last 7 calendar days (today excluded — see note below) | Alert checks, short-term traffic trends |
| 28d | Last 28 calendar days | Default for site overview, keyword trends, MCP tools |
| 90d | Last 90 calendar days | Content decay detection, long-term ranking trends |
Day boundaries are UTC. A “day” runs from 00:00:00 UTC to 23:59:59 UTC.
GSC data lag. Google Search Console data typically arrives 2–3 days late. The API reads the latest available snapshot, so results for the most recent 3 days may be incomplete or absent. This lag is baked into the ingestion schedule (daily 04:00 UTC).
GA4 data lag. GA4 session data is available the following day (ingested at 04:30 UTC). Today’s sessions are never included in any period range.