Signals Coverage Proof
Signals coverage proof answers one practical question before you use a visitor lane or saved filter:
Do we have enough real people in this result, and did we keep non-human traffic measured separately?
Use it before making site changes, reporting coverage, planning content, or deciding whether a result is large enough to act on. It keeps three views side by side:
| View | What it proves | Why it matters |
|---|---|---|
| Coverage | How many first-party visitors match the saved filter. | You know whether the result is large and fresh enough to use. |
| Lane separation | How much bot-classed traffic was measured separately from humans. | Crawlers, monitors, previews, and automation do not become "people" by accident. |
| Match readiness | How many matched visitors have first-party identity signals. | You can see whether the result has enough first-party signal without exposing raw email or phone values. |
Coverage proof is an API surface: POST /api/signals/proof on the dashboard, authenticated with a dashboard session. There is no dashboard screen that renders it today — call it from a controlled workflow that holds a dashboard session, as below. (The csst_ token for the live Signals Feed is a separate surface with its own UI: Sites → select the property → Install tab → Stream token.)
Beginner Workflow
- Pick the site and date range:
24h,7d,30d, or90d. - Leave filters empty for site-wide proof, or add approved saved filters for a narrower result.
- Check
coverage.segmentVisitorsfor the matched human-visitor count. - Check
coverage.botSuppressedVisitorsandcoverage.botRateto make sure non-human traffic was measured separately. - Check
answerEngine.topCrawledPagesagainstanswerEngine.topHumanPagesto find coverage gaps. - Turn the findings into a site, content, or instrumentation task. Do not expose the internal filter recipe to end users.
Copy-Paste: Request Proof
Run this from a dashboard-authenticated admin page or server route. The browser must already have a valid dashboard session cookie.
async function requestCoverageProof() {
const response = await fetch('/api/signals/proof', {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
siteId: 'site_acd434d3',
name: 'Docs coverage check',
filters: [],
topicIds: [],
range: '30d',
}),
});
if (!response.ok) {
throw new Error(`Proof failed: ${response.status}`);
}
return response.json();
}
Field rules: siteId is optional — omit it (or send null) to prove across every site you can reach. name is trimmed and capped at 160 characters. filters and topicIds default to empty arrays. range is one of 24h, 7d, 30d, 90d and defaults to 30d.
Errors
| Status | Body | Meaning |
|---|---|---|
| 401 | Unauthorized | No valid dashboard session. |
| 404 | No sites available for coverage analysis | The account has no reachable sites. |
| 404 | Site not found | The siteId is not one of your reachable sites. |
| 400 | Validation error + details | Body failed schema validation. |
| 409 | This segment is too large for a synchronous exact proof... + code | Narrow the property, range, or filters and retry. Proof is exact or it fails — it never returns an estimate. |
| 500 | Internal server error | Retry with backoff. |
Reading The Response
{
"proof": {
"generatedAt": 1778880000000,
"name": "Docs coverage check",
"siteId": "site_acd434d3",
"siteNames": ["Example Site"],
"filtersEvaluated": 0,
"topicIdsEvaluated": 0,
"coverage": {
"range": "30d",
"rangeStart": 1776288000000,
"rangeEnd": 1778880000000,
"specificity": "site_scope",
"measuredAgainst": "first_party_visitors",
"totalVisitors": 1420,
"humanVisitors": 1310,
"segmentVisitors": 1310,
"botSuppressedVisitors": 110,
"identifiedPeople": 410,
"humanEvidencePeople": 1310,
"matchablePeople": 390,
"hashedIdentifierPeople": 210,
"consentReadyIdentityPeople": 180,
"totalEvents": 6200,
"humanEvents": 5710,
"botEvents": 490,
"botRate": 7.9,
"coverageScore": 100,
"confidenceScore": 90,
"matchabilityScore": 30,
"identityReadinessScore": 14,
"freshnessScore": 100
},
"answerEngine": {
"aiAgentEvents": 16,
"aiAgentVisitors": 9,
"searchCrawlerEvents": 224,
"searchCrawlerVisitors": 41,
"topCrawledPages": [
{ "path": "/docs/getting-started", "events": 48, "visitors": 18, "avgBotScore": 84 }
],
"topHumanPages": [
{ "path": "/account/settings", "events": 220, "visitors": 96 }
],
"botRegistry": [
{ "name": "Known crawler", "category": "search_crawler", "events": 80, "visitors": 15, "pagesVisited": 9 }
],
"recommendations": [
{
"type": "metadata",
"priority": "high",
"message": "High-human pages are not showing bot coverage yet: /account/settings."
}
]
},
"readiness": {
"coverageReady": true,
"identityCoverageReady": true,
"consentBound": true,
"recommendedUse": "hashed_identifiers",
"blockedReasons": [],
"readyPeople": 180
}
}
}
What The Scores Mean
| Field | Plain-English meaning |
|---|---|
coverage.totalVisitors | All first-party visitors in range, before lane separation. |
coverage.humanVisitors | Visitors eligible for human analysis after bot-classed traffic is suppressed. |
coverage.segmentVisitors | Human visitors that matched the supplied filters, or all human visitors when filters are empty. |
coverage.botSuppressedVisitors | Non-human visitors measured separately from the human result. |
coverage.matchablePeople | Matched visitors with a first-party identifier useful for internal matching checks. |
coverage.hashedIdentifierPeople | Matched visitors with hashed email or phone available. |
coverage.consentReadyIdentityPeople | Matched visitors that have consent-safe hashed identifiers available for the API's readiness output. |
coverage.coverageScore | Matched human visitors compared with the human visitors in scope. |
coverage.confidenceScore | Volume plus specificity; higher means the proof has stronger evidence. |
coverage.matchabilityScore | Share of the result with matchable identity. |
coverage.identityReadinessScore | Share of the result with enough consent-safe identity signal for the API's readiness output. |
coverage.freshnessScore | How recently matched visitors were seen. |
AI/Search Coverage View
Use answerEngine to compare what machines can see with what humans actually use.
function findDiscoveryGaps(proof) {
const crawled = new Set(proof.answerEngine.topCrawledPages.map((page) => page.path));
return proof.answerEngine.topHumanPages
.filter((page) => !crawled.has(page.path))
.map((page) => ({
path: page.path,
action: 'Add structured content, metadata, and edge capture coverage.',
}));
}
This is useful for content and SEO work: a page with human use but no crawler coverage is a practical improvement target.
Hashed-Identifier Readiness
Use readiness to decide whether the result is strong enough for on-site decisions, reporting, or content planning:
function hasEnoughIdentitySignal(proof) {
return (
proof.readiness.identityCoverageReady &&
proof.coverage.consentReadyIdentityPeople > 0 &&
proof.coverage.botSuppressedVisitors >= 0
);
}
If blockedReasons includes no_hashed_identifiers, the result can still be useful for on-site decisions and content planning, but it is not strong enough for identity-backed reporting. If it includes missing_marketing_consent, fix consent capture before relying on identity-style workflows; that reason name mirrors the SDK consent category.
Privacy Notes
- Raw email and phone values are not exposed in Signals coverage proof.
- Dashboard authentication and normal site membership checks apply.
- Bot-classed visitors remain visible in proof and coverage, but stay out of the human result.