---
title: "Leads Data Quality"
method: GET
path: "/api/v1/leads/data_quality"
tags: ["leads", "leads"]
---

# Leads Data Quality

`GET /api/v1/leads/data_quality`

## Request

- Base URL: `https://app.goodleads.club` (the document declares no server; this is the origin it was published from)
- URL: `https://app.goodleads.club/api/v1/leads/data_quality`
- Auth: API key in header `X-API-Key`

Per-state Data Quality scorecard over the customer-facing lead_browse view.

Mechanical conformance checks across four value dimensions — format (state,
phone, email, zip), completeness (registered agent, address), standardization
(canonical vocabulary), and consistency (names in CRM-ready Title Case, not
ALL-CAPS). Returns an overall 0–100 score, the per-dimension breakdown, and
per-check pass rates with sample offenders.

`residual_defects` and `checks_with_residual_defects` ride beside the
headline score: a small failure count (e.g. 159 defects over ~397K
applicable rows) rounds away to a clean 100.0 at both the check and
headline level, so these two fields are how a reader tells "zero defects"
apart from "100.0 that is hiding a known residual" without digging into
`checks[].failed` by hand.

The payload also carries the record-centric **coverage** dimension (the
`coverage` block + `dimensions.coverage`): per pipeline stage, how many
records that brain has never stamped — loaded-but-unprocessed inventory the
value checks cannot see. Each coverage check ships `browse_filters` (a
`missing_stage` filter) that opens the gap cohort on browse/export and
scopes a surgical repair run on the pipeline trigger. Coverage does not
move the headline `score` (see stage_coverage.py for the scoring rules).

The `bad_emails` block counts every known-bad email we hold (ZeroBounce
invalid / spamtrap / abuse / do-not-mail, the email picker's own list):
`primary_bad_contacts` (a bad email in the sellable slot — should be 0),
`set_aside_bad_emails` (kept on purpose so we never pay to check them
again), `all_bad_contacts`, and `never_validated_contacts`. Reported
beside the score, never folded into it.

The `bad_phones` block is the phone-side mirror: `primary_unknown_type_on_fire`
(an unresolved phone line type sold at full `reachability_tier='On Fire'`
confidence — should be 0, the one competitor-mined defect this check exists
to catch), plus `primary_unknown_type_contacts` and
`primary_not_confirmed_live_contacts` for context (some residual there is
normal — a real number with no vendor type/liveness signal).

This is the scoreboard the State GMs read each session and the State Quality
Board renders, so data-quality work has a number to move the way
classification does.

## Query parameters

- `state` string, nullable: State code (e.g. CO, FL). Omit for all states, worst score first.
- `sample_limit` integer: Max sample offenders per check. Default 8 is enough to eyeball a defect in a session. Raise toward the check's failed-count (see the `failed` field on each check) when the goal is remediation — e.g. collecting every failing lead_ref to drive a scoped, force-recomputed pipeline re-run via entity_id paste — not just a spot-check sample. The warmer already tallies offenders up to this ceiling for every state; a read only slices the cached list, so raising it changes the payload size, never the cost.
- `fresh` boolean: Ask the background warmer to recompute soon. This endpoint serves the in-process cache ONLY and never computes in the request path (the 2026-09-22 API OOM was a request-path recompute meeting a retrying caller); the response is still the last known card.

## Response `200`

Successful Response

- unknown

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/goodleads/apis/formation-gtm-api.md) · [All operations](https://skmtc.dev/goodleads/apis/formation-gtm-api/llms.txt) · [OpenAPI document](https://skmtc.dev/goodleads/apis/formation-gtm-api/revisions/2068d13a82cf?raw)
