Universal Lead Schema (ULS) guide
ULS v1.0 is the canonical envelope every Switchly request and webhook payload uses. Nine blocks, top-level keys on the JSON body:
lead_metadata | consumer | product_specific | compliance | scoring
routing | delivery | fraud | custom
lead_metadata, consumer, and compliance are required on ingest.
The other six are optional and Switchly fills them in if you don't.
lead_metadata
The envelope — who, what, when.
| field | type | required | notes |
|---|---|---|---|
lead_id |
UUID v4 string | ✓ | Your platform's UUID. Switchly will mint its own to use internally and return it as lead_id in the response. |
timestamp |
ISO 8601 | ✓ | When the consumer actually generated the lead on your platform, not when you're calling Switchly. |
product_line |
enum | ✓ | One of auto, home, renters, life, medicare, final_expense, commercial, health. |
source_platform |
string | — | Your platform slug. Switchly fills it in from your auth context if omitted. |
source_campaign |
string | — | Ad group, landing page slug, keyword — whatever you want to roll up by. |
lead_type |
enum | — | form_fill, inbound_call, warm_transfer, aged. Drives the scoring engine's intent calculation. |
intent_level |
enum | — | high / medium / low. Coarse human override. |
priority |
1–10 | — | 1 is highest. Used as a tiebreaker. |
tags |
string[] | — | Free-form. Shows up in audit logs. |
consumer
Identity + reachability. Switchly never charges for unreachable leads, but the more you provide, the better the contactability score.
{
"first_name": "Jordan",
"last_name": "Rivers",
"dob": "1958-04-12",
"gender": "F",
"email": "jordan@example.test",
"phone": "+1-555-0100",
"alt_phone": "+1-555-0101",
"address": {
"street": "123 Pine St",
"city": "Indianapolis",
"state": "IN",
"zip": "46202"
}
}
dob is mandatory for Medicare and Life routing; recommended everywhere.
address.state is the primary routing key — Switchly matches it against
each agent's active state licenses.
product_specific
Free-form per-product data. Switchly stores it as-is and surfaces it on the delivery payload so the receiving agent sees what you collected. Examples:
// medicare
"product_specific": {
"current_plan": "Original Medicare",
"dual_eligible": false,
"special_enrollment": true
}
// auto
"product_specific": {
"vehicles": [{"year": 2021, "make": "Toyota", "model": "Camry"}],
"drivers": [{"age": 34, "accidents": 0}]
}
No schema is enforced here — that's deliberate. Add or remove fields without coordinating a Switchly schema change.
compliance
TCPA + fraud-detection signals. tcpa_consent is the only required
field, but routing engines treat leads without timestamps and source URLs
as second-class.
{
"tcpa_consent": true,
"tcpa_timestamp": "2026-05-22T14:59:30Z",
"tcpa_source_url": "https://example.test/quote",
"recording_url": null,
"ip_address": "198.51.100.42",
"user_agent": "Mozilla/5.0 ...",
"language": "en"
}
The ip_address field also powers velocity-burst fraud detection — five+
leads from the same IP within an hour trips a lead.flagged_fraud event.
scoring
The five canonical 0–1 floats:
| field | what it measures |
|---|---|
quality_score |
Overall lead quality (composite). |
intent_score |
Buyer intent. |
contactability_score |
Likelihood the agent can reach the consumer. |
fraud_score |
Risk of fraud (higher = worse). |
confidence |
How complete the data is. |
If you ship these, Switchly trusts them. If you omit the block (or any
field within it) Switchly auto-scores via its deterministic engine after
ingest. See the RoutingScore formula for how
each component is weighted to rank candidate agents.
routing
Optional hints from your platform. Switchly uses these as preferences, not hard rules — agent eligibility + scoring still apply.
{
"preferred_agents": [42, 17],
"excluded_agents": [],
"geo_rules": {
"zip": ["46202", "462"],
"state": ["IN", "OH"],
"radius_miles": 50
},
"delivery_method": "webhook",
"max_retries": 5,
"retry_delay_seconds": 30
}
delivery, fraud
These are derived at emit time — you should never need to send them.
They appear on GET /api/v1/leads/{id}/status and on outbound webhooks.
delivery aggregates per-attempt rows from lead_assignments:
{
"status": "assigned",
"attempts": 1,
"last_attempt": "2026-05-22T15:01:14Z",
"delivered_to": ["7"],
"errors": []
}
fraud aggregates the append-only lead_fraud_signals table:
{
"duplicate_within_network": true,
"duplicate_ids": ["abc…", "def…"],
"velocity_flags": [],
"data_mismatch_flags": [],
"blacklist_flags": []
}
custom
Anything you want. Switchly persists it and round-trips it. Use this for platform-specific extensions before requesting a ULS field promotion.
{
"internal_ref": "MAH-2026-Q2-1234",
"rep_id": "kelly-l",
"experimental_flag": true
}