The dangerous version of a lead verification API is a single boolean. It hides which evidence was checked, which provider failed, which Business policy ran, and whether the caller can safely retry. A useful contract preserves those boundaries before a Lead reaches any Destination.
The five layers of a safe response contract
| Layer | Required fields | Question answered | Must stay separate from |
|---|---|---|---|
| HTTP | Status, content type, request or trace ID | Could the API accept and process this request? | The Lead's Validation Outcome |
| Validation Run | Run ID, outcome, reasons, policy version, started and completed times | Is this Lead safe to route under the stated policy? | Any one field or provider result |
| Validation Check | Type, status, provider, confidence, reasons, checked time | What evidence did this field or signal produce? | The overall decision |
| Failure | Scope, stable code, retryable flag, safe detail | Did input, policy, provider, or infrastructure fail? | Negative contact evidence |
| Next action | Deliver, hold for review, do not deliver, or retry according to policy | What may the routing system do now? | Proof that a Delivery succeeded |
This is an architectural recommendation, not a universal wire format. If the API needs
a standard HTTP error body,
RFC 9457 defines
application/problem+json and the type, status,
title, detail, and instance members. Keep that
transport error beside, not inside, a successful domain response.
A synthetic lead validation response
The example below contains no personal data. It shows a locally valid phone check, an unavailable email provider, and a review outcome. The timeout remains visible as missing evidence instead of being converted into an invalid email.
{
"validation_run_id": "vr_demo_01",
"outcome": "review",
"reason_codes": ["EMAIL_EVIDENCE_UNAVAILABLE"],
"policy_version": "business-default-v3",
"next_action": "hold_for_review",
"started_at": "2026-08-24T08:30:00Z",
"completed_at": "2026-08-24T08:30:02Z",
"checks": [
{
"type": "phone",
"status": "valid",
"normalized_value": null,
"value_redacted": true,
"provider": "local",
"confidence": "high",
"checked_at": "2026-08-24T08:30:00Z",
"reason_codes": ["PHONE_RANGE_VALID"]
},
{
"type": "email",
"status": "unknown",
"normalized_value": null,
"value_redacted": true,
"provider": "example-provider",
"confidence": null,
"checked_at": "2026-08-24T08:30:02Z",
"reason_codes": ["PROVIDER_TIMEOUT"],
"error": {
"code": "EMAIL_PROVIDER_TIMEOUT",
"retryable": true
}
}
]
}
Real responses may return authorized normalized values, but every consumer does not need them. Prefer field presence or a redacted representation when routing only needs status and reasons.
1. Preserve independent Validation Checks
Phone, email, address, name, duplicate, and source evidence have different meanings. A
common check envelope makes them comparable without pretending they are the same
signal. Use the stable Lucidity statuses valid, invalid,
risky, unknown, and missing, then attach
check-specific reason codes.
Provider taxonomies should be preserved at the boundary and mapped deliberately. For
example,
Twilio Lookup v2 returns a
valid field and optional validation errors for invalid number ranges. The
current
ZeroBounce v2 email response
uses statuses including valid, invalid,
catch-all, and unknown, plus more specific sub-statuses.
Those vendor values are evidence, not a universal routing policy.
2. Return one explicit Validation Outcome
| Outcome | Meaning | Allowed next action | What it does not prove |
|---|---|---|---|
qualified |
Forwardable under the recorded Business policy | Create Delivery attempts for eligible Destinations | That any Delivery succeeded |
review |
Evidence is ambiguous, conflicting, duplicate, or incomplete | Hold for an operator or an explicit review policy | That the Lead is bad |
blocked |
High-confidence evidence says the Lead is not forwardable | Do not deliver automatically | That every submitted field was invalid |
error |
The Validation Run could not complete | Apply the recorded Business fallback or retry policy | That the Lead failed validation |
The decisive boundary is between blocked and error. A
blocked Lead has negative evidence. An error describes a failed decision process.
Lucidity's
Lead Validation documentation
explains the same contactability-first principle: provider unavailability records
evidence but should not automatically erase a locally valid contact path.
3. Make partial provider failure recoverable
- Run deterministic checks first. Normalize and validate fields that do not need an external provider.
- Give providers a bounded budget. Record their start time, duration, package, and terminal state.
- Map absence to unknown. A timeout, unavailable package, or no-data response is not an invalid contact value.
- Combine the evidence under one policy version. The aggregator, not an individual provider, produces the overall outcome.
- Expose a safe next action. The caller should not have to reverse engineer whether to deliver, hold, stop, or retry.
This matters because provider APIs expose uncertainty differently. ZeroBounce, for
example, documents unknown sub-statuses for greylisting, temporary mail-server errors,
non-response, and timeouts. Collapsing all of them to invalid
would convert temporary infrastructure evidence into a permanent routing decision.
4. Separate request errors from Lead outcomes
Malformed JSON, missing authorization, an unsupported field, a provider timeout, and a
blocked Lead belong to different layers. Use HTTP semantics for request and service
failure. Return a normal Validation Run representation when the API completed the
domain decision, even if that outcome is blocked or review.
A client must never interpret every non-successful provider call as
blocked, or every blocked Lead as an HTTP server error. Transport tells
the caller whether the operation worked. The Validation Outcome tells routing what
may happen to the Lead.
RFC 9457 also warns that problem details are not a debugging tool. Use a stable public problem type and a support-safe instance identifier, but keep stack traces, provider credentials, internal hosts, and raw upstream bodies out of the response.
5. Design timeouts and retries around idempotency
Accept a caller-supplied idempotency key or request identifier when starting a Validation Run. Repeating the same request should return or resume the same logical run, not create duplicate Leads or duplicate downstream work. Record retry state at the failed check so a later attempt does not discard already completed evidence.
Mark errors retryable only when the failure is plausibly transient. A timeout or rate limit may qualify; invalid input and unsupported policy do not. Use bounded attempts, backoff, and a total decision deadline chosen for the workflow. There is no universal retry schedule, so publish the actual policy beside the API contract and test its interaction with routing.
6. Keep the reason taxonomy small and stable
| Reason family | Example | Use | Avoid |
|---|---|---|---|
| Field evidence | PHONE_RANGE_INVALID |
Explain one check result | Embedding the submitted value in the code |
| Cross-check evidence | DUPLICATE_RECENT |
Support review or strict suppression policy | Assuming every repeat inquiry is unwanted |
| Policy requirement | REQUIRED_CONTACT_MISSING |
Explain why the Business policy did not qualify | Hiding the policy version |
| Provider absence | PROVIDER_TIMEOUT |
Drive retry and observability | Mapping absence to field invalidity |
A reason code should exist because it drives routing, review, reporting, retry, or calibration. Keep human-readable explanations separate so wording can improve without breaking clients.
7. Minimize data in requests, responses, and logs
Send each provider only the fields required for its documented check. Do not forward an entire raw Lead payload to a phone or email service. Return normalized values only to authorized consumers, set retention rules for raw provider evidence, and use opaque identifiers in support tooling.
The OWASP Logging Cheat Sheet recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, sensitive personal data, and other secrets instead of recording them directly. For a validation service, useful logs usually contain the run ID, Business ID, check type, provider, duration, status, reason code, retry count, and policy version. They do not need a full phone number, email address, API key, or raw response body.
8. Measure the decision, not just API uptime
Service health is necessary, but it does not show whether the policy is holding too many reachable people or passing weak evidence. Report each metric with a denominator and a stated time window:
- Validation Outcomes by Business and policy version
- Per-check valid, invalid, risky, unknown, and missing rates
- Provider error and no-data rates by check type
- Median, p95, and p99 Validation Run duration
- Review overrides and their original reason codes
- Delivery attempts and successes after qualification
Keep Lucidity Lead, Validation, and Delivery records as first-party facts. GA4 can add traffic context, but it is not the source of truth for whether a Lead was contactable, held, blocked, routed, or successfully delivered.
Lead Validation API test matrix
| Case | Expected check evidence | Decision assertion |
|---|---|---|
| Valid phone, invalid email | Two preserved check results | Apply the configured contact requirement, not a universal block |
| Provider timeout | Unknown check with provider error and retryability | Never rewrite the timeout as invalid contact evidence |
| Recent duplicate | Duplicate reason with the configured Business window | Review by default unless strict suppression is explicit |
| Malformed request | No Validation Run created | Return a safe HTTP problem, not a blocked outcome |
| Repeated idempotency key | Same logical run and completed evidence | No duplicate Lead or Delivery work |
| Conflicting strong evidence | Every original check and reason retained | Hold for review under the stated policy |
Start with the phone validation API guide for field-level evidence, use the Lead qualification software guide for category choices, and keep successful routing distinct from Delivery proof.
Primary sources checked August 24, 2026
- IETF RFC 9457: Problem Details for HTTP APIs
- Twilio Lookup v2 API
- ZeroBounce v2 single email validator API
- ZeroBounce v2 status codes
- OWASP Logging Cheat Sheet
- Lucidity documentation: Lead Validation
Frequently asked question
What contract should a validation API return so routing decisions are explainable and recoverable?
Return an overall Validation Outcome plus independent field-level checks. Each check should preserve its status, normalized-value availability, provider, confidence, reason codes, and timestamp. Keep provider or transport errors separate from negative contact evidence, expose a safe next action, and include stable identifiers and policy versions so the decision can be audited or retried without duplicating work.
Gate Delivery with evidence you can explain
Bring one Business policy and the phone, email, duplicate, or source checks it needs. See how Lucidity records each Validation Check, holds ambiguous evidence for review, and routes from one explicit Validation Outcome.
Request a Lead Validation walkthrough