Skip to main content

Autobind Lead & Call Acquisition API

Download as Markdown — for AI-assisted integration (Claude, Cursor, etc.)

Last updated: 2026-08-05.

Overview

JSON API for submitting auto insurance leads and call transfers via ping-post. You ping with bid parameters (demographics, drivers, vehicles) to get a price, then post the consumer's PII if you accept our bid. Compliance proof (TrustedForm or SafeBind) can go on either request but we strongly recommend the ping.

Two media types:

  • lead — Form-submitted consumer data. We buy the data.
  • call — Live call transfer. We provide a phone number to transfer the consumer's call to.

Base URL: https://api.autobind.ai


Quick Start

Lead — ping then post

# 1. Ping with consumer data (no PII)
curl -X POST https://api.autobind.ai/leads/ping \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "lead",
"lead_type": "exclusive",
"campaign_name": "google-auto-full-form",
"ip_address": "73.162.100.50",
"user_agent": "Mozilla/5.0 Chrome/120.0.0.0",
"media_source": "Google",
"landing_page": "https://example.com/quote",
"lead_created_at": "2026-03-23T14:30:00Z",
"state_abbreviation": "TX",
"zip": "75201",
"language": "en",
"currently_insured": true,
"sr_twenty_two": false,
"home_ownership": false,
"drivers": [{ "gender": "male", "age": 35, "marital_status": "married", "license_status": "active" }],
"vehicles": [{ "year": 2021, "make": "Toyota", "model": "Camry", "commercial_use": false }]
}'
# → { "status": "bid", "bid_id": "...", "price": "4.20" }

# 2. Post PII (use the bid_id from step 1)
curl -X POST https://api.autobind.ai/leads/post \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "lead",
"bid_id": "BID_ID_FROM_PING",
"trusted_form_cert_url": "https://cert.trustedform.com/abc123",
"first_name": "John", "last_name": "Doe",
"contact_phone": "2145559012", "email": "john@example.com",
"street_address": "123 Main St", "city": "Dallas",
"drivers": [{ "first_name": "John", "last_name": "Doe", "relationship_to_policyholder": "self", "birth_date": "1990-06-15" }]
}'
# → { "status": "accepted", "bid_id": "..." }

Call — ping then post

# 1. Ping with minimal data
curl -X POST https://api.autobind.ai/leads/ping \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "call",
"external_id": "your-unique-id-123",
"state_abbreviation": "TX",
"currently_insured": true,
"language": "en"
}'
# → { "status": "bid", "bid_id": "...", "price": "12.40", "transfer_phone": "8773119191", "minimum_call_duration": 90 }

# 2. Post with consumer's phone number (use the bid_id from step 1)
curl -X POST https://api.autobind.ai/leads/post \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "call",
"bid_id": "BID_ID_FROM_PING",
"dial_in_phone": "2145559688"
}'
# → { "status": "ok" }
# Transfer the consumer to transfer_phone from step 1.
# Call must last at least minimum_call_duration (90) seconds to qualify for payment.

How It Works

Ping = bid parameters. Post = PII. All demographic, insurance, driver, vehicle, and incident data is sent on the ping — these are the parameters we use to calculate your bid. The post contains only personally identifiable information (names, phones, email, address) and driver identity. Compliance proof (trusted_form_cert_url or safebind_cert_url) can be sent on either request, but we strongly recommend including it on the ping — it's available at form fill time and lets us validate consent before you send PII.

Lead Flow

  1. Consumer submits a form on your site
  2. You ping us with the required fields (state, insured status, drivers, vehicles) plus any optional fields you have — more data improves bid accuracy
  3. We return a bid_id and price, or decline
  4. If you accept our bid, you post the consumer's PII (name, phone, email, address) along with the bid_id. Bid parameters and compliance proof from the ping are already stored with the bid
  5. We accept or reject the post — use GET /leads/status/{bid_id} to check status later

Call Flow

  1. Consumer wants an auto insurance quote (via your site or phone system)
  2. You ping us with the required fields (state_abbreviation, currently_insured, external_id) plus any additional data you have — driver/vehicle info, zip, credit status, etc. all improve bid accuracy
  3. We return a bid_id, price, transfer_phone (the number to transfer the consumer's call to), and minimum_call_duration (seconds required for payment)
  4. If you accept our bid, you post the bid_id along with the consumer's phone number (dial_in_phone) — we need this to match the incoming call to your bid. If the call originated from a form and you have full lead data (name, address, drivers, vehicles, etc.), we strongly recommend including it in the post
  5. Transfer the consumer to transfer_phone
  6. Use GET /leads/status/:id later to check whether the call met the minimum duration and is billable

Why does the post require the consumer's phone number? When the consumer calls our transfer_phone, we match the incoming caller ID to the dial_in_phone you provided. This is how we connect the call to your bid.


Authentication

All requests require your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Keys are issued per partner during onboarding. The key identifies your account — no partner ID is needed in the request body. This is a server-to-server API — CORS is not supported, so do not call it from browser-side code.


Endpoints

MethodPathPurpose
POST /leads/pingSubmit partial data, receive a bid or decline
POST /leads/postAccept the bid — submit full data (leads) or consumer phone (calls)
GET /leads/status/:idCheck the status of an accepted lead or call

POST /leads/ping

Submit partial consumer data to receive a bid price. Response time < 200ms. No side effects — no data is stored until you post.

Call Ping — Request

Minimal required fields. Providing driver/vehicle data is optional but improves bid accuracy.

curl -X POST https://api.autobind.ai/leads/ping \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "call",
"external_id": "your-unique-id-123",
"state_abbreviation": "TX",
"currently_insured": true,
"language": "en"
}'

Call Ping — Rich Request (optional driver/vehicle data)

Including driver and vehicle data is optional for calls, but improves bid accuracy and the price we return.

curl -X POST https://api.autobind.ai/leads/ping \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "call",
"external_id": "your-unique-id-789",
"state_abbreviation": "CA",
"zip": "90210",
"currently_insured": true,
"language": "en",
"trusted_form_cert_url": "https://cert.trustedform.com/abc123def456",
"drivers": [
{
"gender": "female",
"birth_date": "1988-04-12",
"marital_status": "married",
"license_status": "active"
}
],
"vehicles": [
{ "year": 2022, "make": "Honda", "model": "Civic", "commercial_use": false }
]
}'

Call Ping — Bid Response

{
"status": "bid",
"bid_id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "your-unique-id-123",
"price": "12.40",
"transfer_phone": "4103989038",
"minimum_call_duration": 90
}
  • bid_id — Include this in your post request
  • external_id — Your ID echoed back so you can match this response to your consumer
  • transfer_phone — The phone number to transfer the consumer's call to (10 digits, no country code)
  • minimum_call_duration — Seconds the call must last to qualify for payment

Lead Ping — Request

No PII required — only bid parameters (demographics, insurance, drivers, vehicles). drivers[0] is always the policyholder. Each driver can include an incidents array.

curl -X POST https://api.autobind.ai/leads/ping \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"media_type": "lead",
"lead_type": "exclusive",
"external_id": "your-unique-id-456",
"campaign_name": "google-auto-full-form",
"ip_address": "73.162.100.50",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
"media_source": "Google",
"landing_page": "https://example.com/auto-insurance",
"lead_created_at": "2026-03-22T14:30:00Z",
"state_abbreviation": "TX",
"zip": "75201",
"language": "en",
"currently_insured": true,
"sr_twenty_two": false,
"home_ownership": false,
"credit_status": "average",
"trusted_form_cert_url": "https://cert.trustedform.com/abc123def456",
"drivers": [
{
"gender": "male",
"birth_date": "1990-06-15",
"marital_status": "married",
"license_status": "active",
"incidents": [
{
"type": "violation",
"violation_type": "driving_under_the_influence"
}
]
},
{
"gender": "female",
"age": 33,
"marital_status": "married",
"license_status": "active",
"incidents": [
{
"type": "accident",
"incident_date": "2025-06-15",
"fault_status": "majority_at_fault",
"damage_type": "property_and_bodily_injury"
}
]
}
],
"vehicles": [
{ "year": 2021, "make": "Toyota", "model": "Camry", "commercial_use": false },
{ "year": 2023, "make": "Honda", "model": "CR-V", "commercial_use": false }
]
}'

Lead Ping — Bid Response

{
"status": "bid",
"bid_id": "660f9500-a12c-41d4-b827-557766550000",
"external_id": "your-unique-id-456",
"price": "4.20"
}

Lead bids do NOT include transfer_phone or minimum_call_duration.

price is a string to preserve decimal precision. Always 2 decimal places. Parse as a decimal type, not a float.

Decline Response (both types)

{
"status": "decline",
"reason": "no_bid",
"external_id": "your-unique-id-123",
"message": "Data validated successfully but no bid is available for this lead."
}

message is optional and human-readable — suitable for logging but not for display to end users. The text may change without notice; always use reason for programmatic logic.

Decline reasons:

ReasonMeaning
no_bidData validated successfully but no bid is available. This covers all cases where we choose not to buy (state not supported, budget exhausted, doesn't meet criteria, outside operating hours, zip issues, etc.)

Handling Ping Responses

response = ping(consumer_data)

if response.status == "bid":
// We want to buy — post PII to accept
post_response = post(bid_id, pii)
if post_response.status == "accepted" or post_response.status == "ok":
// Success — for calls, transfer consumer to transfer_phone from ping
else:
// Rejected — check post_response.reason (duplicate, expired, etc.)

else if response.status == "decline":
// We don't want this lead/call — try another buyer or skip
log(response.reason, response.message)

else if response.status == "error":
// Bad request — fix the payload based on response.errors[]
log(response.errors)

POST /leads/post

Submit full data after winning the auction. Include the bid_id from the ping response.

Bid expiry:

  • Lead bids expire after 90 seconds
  • Call bids expire after 60 seconds

Posting after expiry returns {"status": "rejected", "reason": "bid_expired"}.

Post — Accepted Responses

Lead:

{ "status": "accepted", "bid_id": "660f9500-...", "external_id": "your-id" }

Call:

{ "status": "ok" }

Once accepted, transfer the consumer to the transfer_phone returned in the ping response. The call must last at least minimum_call_duration seconds to qualify for payment.

Rejected Response

{ "status": "rejected", "reason": "duplicate_phone" }
ReasonMeaning
invalid_bidbid_id not found, wrong partner, or media type mismatch
bid_expiredBid has expired (leads: 90s, calls: 60s)
duplicate_phonePhone number seen in last 30 days (leads)
duplicate_callSame phone had a billable call from your account in the last hour (calls)
missing_consent_proofNo proof of consent — neither trusted_form_cert_url nor safebind_cert_url (leads only)
failed_secondary_evaluationLead data failed our evaluation (leads only)

Compliance Proof

Proof of consent is required for all leads. Send one of these — on either the ping or the post (not both requests needed):

  • trusted_form_cert_url — a TrustedForm certificate URL from ActiveProspect.
  • safebind_cert_url — a SafeBind proof-of-consent URL, in the form https://api.safebind.ai/c/<token>.

Either proves the consumer consented on your form. Leads carrying neither are rejected with "missing_consent_proof".

Send one, not both. If you send both we verify the TrustedForm certificate and ignore the SafeBind URL — we don't pay two providers to answer the same question. Pick whichever you already capture.

We strongly recommend sending your consent proof (and all compliance fields) on the ping. Both providers generate their certificate at form fill time, before you have PII — so it's available when you ping. Sending it early means your post only needs bid_id, PII, and driver identity.


Field Reference

All ping and post fields in one table. ✅ = required, ◐ = conditionally required (the Description gives the condition), ○ = optional, — = not applicable.

See the key principle above: ping = bid parameters (+ compliance), post = PII.

Call posts: Only media_type, bid_id, and dial_in_phone are required. All other fields are optional — but including lead data improves conversion rates and the price we're willing to pay on future bids.

FieldTypeLeadCallDescription
PingPostPingPost
Core FieldsLead
Ping
Lead
Post
Call
Ping
Call
Post
media_type"lead" / "call"
bid_idUUIDFrom ping response
external_idstring (100)Your ID — echoed in responses. Omitted from response if not provided
state_abbreviationstring (2)"TX" — 2 uppercase letters
zipstring (5)5 digits, e.g. "75201"
language"en" / "es""en" = English, "es" = Spanish. Required for calls
currently_insuredbooleanDoes the policyholder currently have an active auto insurance policy?
home_ownershipbooleanDoes the policyholder own their home?
sr_twenty_twobooleantrue if any driver in the household has an SR-22, FR-44, or similar filing. For per-driver detail, use .sr_twenty_two on each driver
lead_typeenum"exclusive" or "shared". Leads only. Whether this lead is sold to one buyer or multiple. Defaults to "shared" if omitted — send "exclusive" for higher bids
dial_in_phonestring (10 digits)Calls only. The caller ID we use to match the incoming call to your bid
Compliance & ConsentLead
Ping
Lead
Post
Call
Ping
Call
Post
trusted_form_cert_urlstring (500)Proof of consent — required for leads (this or safebind_cert_url) — send on ping or post (ping strongly recommended). TrustedForm cert URL proving consumer consent
safebind_cert_urlstring (500)Alternative to trusted_form_cert_url — SafeBind proof-of-consent URL (https://api.safebind.ai/c/&lt;token>). Send one provider or the other; if both arrive we verify the TrustedForm cert and ignore this
tcpa_languagestring (2000)TCPA consent text shown on the form. We encourage sending this on the ping
tcpa_jsonstring (5000)Structured TCPA consent data (includes consumer IP, timestamp). We encourage sending this on the ping
leadid_tokenstringJornaya LeadiD token. We encourage sending this on the ping
Submission MetadataLead
Ping
Lead
Post
Call
Ping
Call
Post
ip_addressstringConsumer's IP
user_agentstring (500)Browser user agent
lead_created_attimestampYYYY-MM-DDTHH:mm:ssZ. For calls originated from a form, include the timestamp of the original form fill
Traffic & CampaignLead
Ping
Lead
Post
Call
Ping
Call
Post
source_typeenum"direct" = you generated this lead on media you own and operate. "affiliate" = a third party generated it (affiliate, sub-publisher, network, or another marketplace)
affiliate_namestring (128)Required when source_type is "affiliate"; omit when "direct". The name of the business that generated the lead — legal or DBA, e.g. "Example Quotes LLC"
affiliate_idstring (64)Optional. Your internal identifier for that same business. Send it only if it is stable — the same business getting the same ID every time is what makes it useful
sub_idstring (30)Deprecated (see changelog). An opaque path, creative, or placement token within one source. Alphanumeric, hyphens, underscores
campaign_namestring (100)
media_sourcestringThe platform the media was bought on — not the channel type. Free text, so the long tail is covered: "google" "bing" "facebook" "instagram" "tiktok" "youtube" "snapchat" "reddit" "pinterest" "taboola" "outbrain" "yahoo". Send traffic_channel alongside it — media_source: "google" with traffic_channel: "sem" says something media_source alone can't
traffic_channelenum"sem" paid search · "organic_search" unpaid search · "display" · "native" content-recommendation widgets (Taboola, Outbrain) · "video" YouTube, CTV, pre-roll · "social" · "email" · "sms" · "contextual" contextual-targeted media · "other" a channel you can identify that isn't listed here — use this rather than leaving the field empty.

Need a value that isn't listed? Ask the Autobind team and we'll add it.
Still accepted, mapped automatically: "cpc" and "search""sem", "organic""organic_search"
placement_typeenum"thank_you_page" "leave_behind" "form_page".
Need a value that isn't listed? Ask the Autobind team and we'll add it.
landing_pagestring (500)URL consumer came from
search_keywordstring
Policyholder Contact & Address (post only)Lead
Ping
Lead
Post
Call
Ping
Call
Post
first_namestring (100)
middle_namestring (100)
last_namestring (100)
street_addressstring (200)Including unit/apt
citystring (100)
contact_phonestring (10 digits)Phone the policyholder consented to be contacted on. For calls, often the same as dial_in_phone — but may differ if the consumer calls from one number and consents to contact on another
mobile_phonestring (10 digits)Only if consumer consented to mobile contact
daytime_phonestring (10 digits)
evening_phonestring (10 digits)
emailstring (100)
Demographics & Insurance (Policyholder)Lead
Ping
Lead
Post
Call
Ping
Call
Post
credit_statusenumPolicyholder's self-reported credit rating: "excellent" "above_average" "average" "below_average" "poor"
residence_typeenumPolicyholder's dwelling type: "single_family_home" "townhouse" "condo" "apartment" "mobile_home" "other"
months_at_addressnumberMonths at current address. Convert years to months (e.g. 3 years = 36)
current_companyenumCurrently insured policyholder's insurance company: "Allstate" "Geico" "Progressive" ... see TypeScript types
current_company_tenure_monthsnumberMonths with current insurer. Convert years to months (e.g. 3 years = 36)
current_policy_expiresstringYYYY-MM-DD. Must be today or later, and no more than 1 year in the future
current_bi_per_personnumberCurrent bodily injury limit per person in dollars, e.g. 30000
current_bi_per_accidentnumberCurrent bodily injury limit per accident in dollars, e.g. 60000
continuous_insurance_durationenumHow long continuously insured: "less_than_6_months" "6_months_to_1_year" "1_to_2_years" "2_to_5_years" "5_plus_years"
lapse_durationenumNot currently insured only. How long without insurance: "1_to_15_days" "16_to_30_days" "31_days_to_6_months" "6_months_to_1_year" "1_to_4_years" "5_plus_years"
lapse_reasonenumNot currently insured only. Why the policyholder lapsed: "military" "no_vehicle" "no_license" "no_need" "other"
coverage_typeenumPolicyholder's desired coverage level for the new policy (not current): "state_minimum" "basic" "superior" "premium"
policy_start_datestringPolicyholder's desired start date for the new policy (YYYY-MM-DD)
drivers[] — Driver Array (1–6, policyholder = drivers[0])Lead
Ping
Lead
Post
Call
Ping
Call
Post
drivers[]arrayPing: bid parameters. Post: PII only (names, relationship, license number)
.first_namestring (100)
.middle_namestring (100)
.last_namestring (100)
.relationship_to_policyholderenum"self" "spouse" "child" "parent" "sibling" "other""self" must always be drivers[0]
.birth_datestringYYYY-MM-DD. Ping: provide birth_date or age (at least one). Post: include if available — improves conversion, and ultimately pricing
.agenumber (14–99)Driver's age in years. Use instead of birth_date if PII is a concern on pings
.genderenum"male" "female"
.marital_statusenum"single" "married" "divorced" "separated" "widowed" "domestic_partnership" "civil_union"
.us_resident_past_twelve_monthsbooleanHas driver been a US resident for the past 12 months?
.license_statusenum"active" "suspended" "revoked" "expired" "permit" "no_license". Default "active" if not collected
.license_state_or_countrystringUS: "CA". Foreign: "Mexico"
.license_numberstring (100)
.age_first_licensednumber (14–99)Age when first licensed
.first_licensed_datestringYYYY-MM-DD — Date first licensed
.suspended_or_revoked_past_five_yearsbooleanLicense suspended or revoked in the past 5 years?
.suspension_reasonenum"driving_related" "non_driving_related" "failed_medical_exam" "failed_exam" "failed_to_comply"
.employment_statusenum"company" "self" "military" "government" "retired" "student" "homemaker" "unemployed"
.educationenum"less_than_high_school" "vocational" "high_school" "high_school_pursuing_bachelors" "associate" "associate_pursuing_bachelors" "bachelors" "bachelors_pursuing_graduate" "masters" "doctors" "lawyer" "phd"
.occupationstring (100)Driver's occupation
.industryenum"financial" "agriculture" "arts" "assistants" "automotive" "cleaning" "computers" "construction" "counseling" "education" "engineering" "executives" "health" "law" "operators" "postal" "maintenance" "service" "food" "sales" "science" "travel"
.government_employment_typeenum"federal_employee" "city_state_employee"
.student_typeenum"high_school_student" "technical_vocational_student" "freshman_undergraduate" "sophomore_undergraduate" "junior_undergraduate" "senior_undergraduate" "graduate_student" "law_student" "medical_student"
.military_affiliationenum"active_duty" "military_retiree" "veteran" "military_academy_cadet" "national_guard" "military_reserves"
.sr_twenty_twobooleanPer-driver SR-22 status. When provided, takes precedence over the top-level sr_twenty_two for this driver
.bankruptcybooleanHas this driver filed for bankruptcy?
.incidents[]array (0–6)Ping-only. Omit if none
drivers[].incidents[] — Incidents per Driver (0–6)Lead
Ping
Lead
Post
Call
Ping
Call
Post
.typeenum"accident" "violation" "claim" — required when incident is provided
.incident_datestringYYYY-MM-DD
.fault_statusenumAccidents only: "not_at_fault" "less_than_50_percent" "majority_at_fault"
.damage_typeenumAccidents only: "property_only" "bodily_injury" "property_and_bodily_injury"
.violation_typeenumViolations only: "driving_under_the_influence" "speeding" "driving_while_using_a_cell_phone" "other"
.violation_type_otherstringIf violation_type is "other", describe the violation
.claim_typeenumClaims only: "theft" "vandalism" "glass_repair" "other"
.claim_amountnumberDollar amount of the claim
vehicles[] — Vehicle Array (1–6)Lead
Ping
Lead
Post
Call
Ping
Call
Post
vehicles[]arrayPing-only. All vehicle data is sent on the ping
.yearnumberMin 1900
.makestring"Toyota"
.modelstring"Camry"
.trimstringe.g. "LE", "SE"
.vinstring (17)Exactly 17 characters
.license_plate_statestring (2)"TX"
.license_platestringLicense plate number
.commercial_usebooleanIs this vehicle used for commercial/business purposes? Default false if not collected
.type_of_commercial_useenum"clergy" "courier_service" "daycare" "delivery_fast_food" "delivery_retail_wholesale" "delivery_route" "delivery_us_mail" "delivery_and_sales" "doctor_professional" "farm_use" "lawyer_professional" "real_estate" "repair_installation" "ridesharing" "sales_multistate" "sales_route" "sales_calls" "social_worker" "transport_people" "travel_to_jobsites" "travel_to_meetings" "visit_clients" "visit_outside_offices". Map rideshare (Uber, Lyft) to "transport_people"
.primary_driverbooleanIs this the primary driver of this vehicle?
.ownershipenum"fully_paid" "financed" "leased"
.vehicle_purchased_newbooleanWas the vehicle purchased new?
.vehicle_purchase_datestringYYYY-MM-DD — Date vehicle was purchased
.annual_mileagenumberEstimated annual miles driven
.current_mileagenumberCurrent odometer reading
.average_daily_mileagenumberAverage miles driven per day
.commute_distancenumber (0–500)One-way commute distance in miles
.commute_days_per_weeknumber (0–7)Days per week driven to work
.alarmbooleanVehicle has anti-theft alarm?
.parkingenum"driveway" "private_garage" "parking_garage" "parking_lot" "street"

Data Mapping Guide

When mapping your internal data to this API:

  1. Match by semantics, not field names — Your license_type may map to our license_status. Your insured may map to our currently_insured. Use field descriptions and enum values to find the correct match, not field name similarity.

  2. Convert state names to 2-letter codes — We require uppercase abbreviations: "TX", not "Texas" or "tx".

  3. Nest incidents under their driver — If your data stores incidents in a flat array or at the root level, associate each incident with the correct drivers[].incidents[] object. Incidents are always per-driver, never top-level.

  4. Drop unmapped fields — If a field in your data has no corresponding field in our schema, omit it. Unknown fields are silently stripped (and returned as warnings in the response).

  5. Use enums exactly as documented — Enum values are lowercase with underscores: "married" not "Married", "driving_under_the_influence" not "DUI". See the TypeScript types for the complete set of valid values.


Detailed Examples

Full realistic payloads showing all nesting. drivers[0] is always the policyholder.

Lead Ping — 2 drivers, 2 vehicles, with incidents

{
"media_type": "lead",
"external_id": "pub-2026-03-23-001",
"campaign_name": "google-auto-full-form",
"ip_address": "73.162.100.50",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
"media_source": "Google",
"landing_page": "https://example.com/auto-insurance-quote",
"lead_created_at": "2026-03-23T14:30:00Z",
"state_abbreviation": "TX",
"zip": "75201",
"language": "en",
"currently_insured": true,
"sr_twenty_two": false,
"home_ownership": false,
"credit_status": "average",
"trusted_form_cert_url": "https://cert.trustedform.com/abc123def456",
"drivers": [
{
"gender": "male",
"age": 40,
"marital_status": "married",
"license_status": "active",
"incidents": [
{
"type": "violation",
"incident_date": "2025-01-10",
"violation_type": "driving_under_the_influence"
}
]
},
{
"gender": "female",
"age": 38,
"marital_status": "married",
"license_status": "active",
"incidents": [
{
"type": "accident",
"incident_date": "2025-06-15",
"fault_status": "majority_at_fault",
"damage_type": "property_and_bodily_injury"
}
]
}
],
"vehicles": [
{ "year": 2021, "make": "Toyota", "model": "Camry", "commercial_use": false },
{ "year": 2023, "make": "Honda", "model": "CR-V", "commercial_use": false }
]
}

Lead Post — PII for the same household

{
"media_type": "lead",
"bid_id": "BID_ID_FROM_PING",
"trusted_form_cert_url": "https://cert.trustedform.com/abc123def456",
"first_name": "John",
"last_name": "Doe",
"contact_phone": "2145559012",
"email": "john.doe@example.com",
"street_address": "123 Main St",
"city": "Dallas",
"language": "en",
"drivers": [
{
"first_name": "John",
"last_name": "Doe",
"relationship_to_policyholder": "self",
"birth_date": "1985-06-15",
"license_number": "12345678"
},
{
"first_name": "Jane",
"last_name": "Doe",
"relationship_to_policyholder": "spouse",
"birth_date": "1987-04-28",
"license_number": "87654321"
}
]
}

Call Post — with optional PII

{
"media_type": "call",
"bid_id": "BID_ID_FROM_PING",
"dial_in_phone": "2145559688",
"first_name": "John",
"last_name": "Doe",
"contact_phone": "2145559688",
"email": "john.doe@example.com",
"street_address": "123 Main St",
"city": "Dallas",
"state_abbreviation": "TX",
"zip": "75201"
}

GET /leads/status/:id

Check the status of an accepted lead or call. The :id is the bid_id from the ping/post responses.

Polling guidance: For calls, poll every 2–5 minutes after the transfer to check if billable has flipped to true. For leads, status is typically final within seconds of posting — a single check after 30 seconds is sufficient. Webhooks are planned for a future release.

Lead Status Response

{
"bid_id": "660f9500-a12c-41d4-b827-557766550000",
"external_id": "your-unique-id-456",
"status": "accepted",
"billable": true,
"created_at": "2026-03-22T14:30:05Z"
}

Call Status Response

{
"bid_id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "your-unique-id-123",
"status": "accepted",
"billable": false,
"call_duration": null,
"minimum_call_duration": 90,
"created_at": "2026-03-22T14:30:05Z"
}

For calls, billable is false until call_duration >= minimum_call_duration.


Error Responses

HTTP 400 — Validation Error

Single error:

{
"status": "error",
"errors": ["'state_abbreviation' is required"]
}

Multiple errors:

{
"status": "error",
"errors": [
"'state_abbreviation' is required",
"Must be exactly 10 digits, numeric only at 'contact_phone'",
"'drivers' must have at least 1 item(s)"
]
}

HTTP 401 — Authentication Error

{
"status": "error",
"errors": ["Invalid or missing API key"]
}

HTTP 429 — Rate Limited

EndpointLimit
POST /leads/ping100 req/sec
POST /leads/post20 req/sec
GET /leads/status/:id50 req/sec

No Retry-After header is included. Wait 1 second before retrying.

HTTP 500 — Internal Error

Safe to retry (see Retry section).


Retry & Idempotency

EndpointIdempotentRetry on
POST /leads/pingYes (no side effects)429, 500, timeout
POST /leads/postYes (one request processes; a retry gets 202 processing mid-flight, then the final result replays)500, timeout
GET /leads/status/:idYes (read-only)429, 500, timeout

Do NOT retry on: 400 (fix your payload), 401 (fix your API key), 200 with "rejected" (the lead was evaluated and rejected — retrying won't change the result).

Retry strategy:

Attempt 1: immediate
Attempt 2: wait 1 second
Attempt 3: wait 3 seconds
Give up.

Timeout guidance:

EndpointRecommended timeout
Ping3 seconds
Post45 seconds
Status5 seconds
Do not set the post timeout below 45 seconds

The post verifies your consent proof with the provider you sent — a network call to ActiveProspect or SafeBind, which retries once on a transient failure. In the worst case that takes about 30 seconds. The previously documented 15 seconds was shorter than the work the endpoint does.

What a retry actually gets, precisely. Exactly one request processes a bid_id. If you retry while the first request is still verifying, you get HTTP 202 {"status": "processing", "retry_after_seconds": 10} — not a verdict, and never a contradictory one. Once that request finishes, the same bid_id replays its final accepted/rejected result to every later retry.

A retry is therefore safe: it can never double-process your lead or return a verdict that later flips. But it cannot make the answer arrive faster, so the correct fix is the longer timeout, not more retries. Treat processing as "keep waiting", never as a rejection.

If processing persists. It is bounded, not open-ended: should a request be interrupted before recording a verdict — an infrastructure fault on our side — the claim is released and your next retry re-processes the lead normally, at the latest about two minutes after the interrupted attempt began. So the escalation rule is simply: keep retrying on the given interval; if you are still seeing processing more than ~3 minutes after your first post, that is not normal — stop retrying and contact us with the bid_id.

The ping moved from 2s to 3s for a smaller reason: when your ping carries a consent-proof URL we now check that the certificate exists before bidding, which is a free call to your provider capped at 1.5 seconds. If it can't answer in time we bid anyway — the check never costs you a bid — but the ping itself can take marginally longer than it used to.


Validation Rules

Phone numbers

All phone fields: exactly 10 digits, numeric only. No dashes, spaces, parentheses, or +1 prefix.

Dates

  • YYYY-MM-DD: birth_date, incident_date, first_licensed_date, current_policy_expires, policy_start_date, vehicle_purchase_date
  • YYYY-MM-DDTHH:mm:ssZ: lead_created_at
  • current_policy_expires must be today or later, and no more than 1 year in the future

State codes

  • state_abbreviation, license_plate_state: 2 uppercase letters — "TX" not "tx" or "Texas"
  • license_state_or_country: US states use 2-letter code ("CA"). For foreign licenses, use the full country name ("Mexico", "India", "United Kingdom").

Zip codes

5 digits. Leading zeros are significant: "06510".

Email

Must contain @ and a domain with .. Max 100 characters.

VIN

If provided, exactly 17 characters.

Language

"en" (English) or "es" (Spanish). Required for call pings, optional for leads.

Arrays

  • drivers: 1–6 (required for lead pings and posts, optional for call pings)
  • vehicles: 1–6 (required for lead pings, optional for call pings, not accepted on posts)
  • incidents: 0–6 per driver

Critical rules

  • drivers[0].relationship_to_policyholder must be "self" on lead posts
  • Proof of consent is requiredtrusted_form_cert_url (TrustedForm by ActiveProspect) or safebind_cert_url (SafeBind), on the ping or the post (ping recommended)
  • Unknown fields are silently stripped and returned as warnings in the response (not rejected)

Versioning

This API is not versioned. Breaking changes (removed fields, changed validation rules, new required fields) will be communicated via email with 30 days notice. Additive changes (new optional fields in responses, new optional request fields) may be made without notice — your integration should ignore unknown response fields.


Test Environment

Append ?environment=test as a query parameter to any endpoint URL (not in the JSON body):

POST https://api.autobind.ai/leads/ping?environment=test
POST https://api.autobind.ai/leads/post?environment=test
GET https://api.autobind.ai/leads/status/{bid_id}?environment=test
BehaviorTestProduction (default)
Bidding (ping)Runs normallyRuns normally
Validation (post)Full validation + schema dry-runFull validation
Evaluation (post)SkippedFull
Data persistedNoYes
Budget consumedNoYes

Test mode validates your payload structure and returns any errors. No data is persisted. Use this to verify your payloads are correctly formatted before going live.


TypeScript Types

Copy this block into your project. All request and response shapes are defined here.

// ── Shared ──

type TrafficChannel =
| "sem" | "organic_search" | "display" | "native" | "video"
| "social" | "email" | "sms" | "contextual" | "other"
// Retired spellings, still accepted and mapped on arrival:
| "cpc" | "search" // → "sem"
| "organic"; // → "organic_search"
type PlacementType = "thank_you_page" | "leave_behind" | "form_page";
type SourceType = "direct" | "affiliate";
type Language = "en" | "es";

// ── Ping Requests ──

interface PingDriver {
gender: "male" | "female";
birth_date?: string; // Provide birth_date or age (at least one)
age?: number; // 14–99. At least one of birth_date or age required
marital_status: MaritalStatus;
relationship_to_policyholder?: "self" | "spouse" | "child" | "parent" | "sibling" | "other";
license_status: "active" | "suspended" | "revoked" | "expired" | "permit" | "no_license";
sr_twenty_two?: boolean; // Per-driver SR-22. When provided, takes precedence over top-level for this driver
incidents?: PingIncident[]; // omit if none
}

interface PingIncident {
type: "accident" | "violation" | "claim";
incident_date?: string; // YYYY-MM-DD
fault_status?: "not_at_fault" | "less_than_50_percent" | "majority_at_fault"; // accidents
damage_type?: "property_only" | "bodily_injury" | "property_and_bodily_injury"; // accidents
violation_type?: ViolationType; // violations
violation_type_other?: string; // if violation_type is "other"
claim_type?: "theft" | "vandalism" | "glass_repair" | "other"; // claims
claim_amount?: number; // claims
}

interface PingVehicle {
year: number;
make: string;
model: string;
commercial_use?: boolean;
}

// Lead ping driver/vehicle — commercial_use is required
// LeadPingDriver is the same as PingDriver — no additional fields
type LeadPingDriver = PingDriver;

interface LeadPingVehicle extends PingVehicle {
commercial_use: boolean;
}

interface CallPingRequest {
media_type: "call";
external_id?: string;
source_type?: SourceType;
affiliate_id?: string; // optional — your stable internal ID for that business, if you have one
affiliate_name?: string; // required when source_type is "affiliate"
sub_id?: string; // deprecated — use affiliate_id
media_source?: string;
traffic_channel?: TrafficChannel;
campaign_name?: string;
placement_type?: PlacementType;
search_keyword?: string;
landing_page?: string;
ip_address?: string;
user_agent?: string;
state_abbreviation: string;
zip?: string;
currently_insured: boolean;
sr_twenty_two?: boolean;
home_ownership?: boolean;
language: Language; // required for calls
credit_status?: CreditStatus;
residence_type?: ResidenceType;
trusted_form_cert_url?: string;
safebind_cert_url?: string;
tcpa_language?: string;
tcpa_json?: string;
leadid_token?: string;
drivers?: PingDriver[];
vehicles?: PingVehicle[];
}

interface LeadPingRequest {
media_type: "lead";
external_id?: string;
source_type?: SourceType;
affiliate_id?: string; // optional — your stable internal ID for that business, if you have one
affiliate_name?: string; // required when source_type is "affiliate"
sub_id?: string; // deprecated — use affiliate_id
media_source?: string;
traffic_channel?: TrafficChannel;
campaign_name: string; // required for leads
placement_type?: PlacementType;
search_keyword?: string;
landing_page?: string;
ip_address: string;
user_agent: string; // required for leads
lead_created_at: string;
state_abbreviation: string;
zip: string;
currently_insured: boolean;
sr_twenty_two: boolean;
home_ownership: boolean; // required for leads
credit_status?: CreditStatus;
residence_type?: ResidenceType;
language?: Language;
trusted_form_cert_url?: string;
safebind_cert_url?: string;
tcpa_language?: string;
tcpa_json?: string;
leadid_token?: string;
lead_type?: "exclusive" | "shared"; // defaults to "shared" if omitted
drivers: LeadPingDriver[]; // required, 1–6
vehicles: LeadPingVehicle[]; // required, 1–6
}

type PingRequest = CallPingRequest | LeadPingRequest;

// ── Ping Responses ──

interface CallBidResponse {
status: "bid";
bid_id: string;
external_id?: string;
price: string; // USD, always 2 decimal places, e.g. "12.40"
transfer_phone: string; // 10 digits, no country code
minimum_call_duration: number;
warnings?: string[]; // e.g. ["Unknown field 'dui' was ignored"]
}

interface LeadBidResponse {
status: "bid";
bid_id: string;
external_id?: string;
price: string; // USD, always 2 decimal places, e.g. "4.20"
warnings?: string[];
}

interface DeclineResponse {
status: "decline";
reason: "no_bid";
external_id?: string;
message?: string; // Human-readable explanation, suitable for logging. Do not display to end users — text may change.
}

type PingResponse = CallBidResponse | LeadBidResponse | DeclineResponse;

// ── Post Requests ──

// Only bid_id + dial_in_phone required. PII accepted for form-originated calls.
interface CallPostRequest {
media_type: "call";
bid_id: string;
dial_in_phone: string; // 10 digits
language?: Language;
trusted_form_cert_url?: string;
safebind_cert_url?: string;
tcpa_language?: string;
tcpa_json?: string;
leadid_token?: string;
first_name?: string;
middle_name?: string;
last_name?: string;
contact_phone?: string;
mobile_phone?: string;
daytime_phone?: string;
evening_phone?: string;
email?: string;
street_address?: string;
city?: string;
state_abbreviation?: string;
zip?: string;
drivers?: LeadPostDriver[]; // Optional driver PII for form-originated calls
}

// Post drivers contain only PII — risk fields are on the ping
interface LeadPostDriver {
first_name: string;
middle_name?: string;
last_name: string;
relationship_to_policyholder: "self" | "spouse" | "child" | "parent" | "sibling" | "other";
license_number?: string;
birth_date?: string; // Include if available — improves conversion, and ultimately pricing
}

interface LeadPostRequest {
media_type: "lead";
bid_id: string;
trusted_form_cert_url?: string; // Proof of consent — this OR safebind_cert_url, ping or post
safebind_cert_url?: string; // SafeBind alternative: https://api.safebind.ai/c/<token>
tcpa_language?: string;
tcpa_json?: string;
leadid_token?: string;
first_name: string;
middle_name?: string;
last_name: string;
contact_phone: string;
mobile_phone?: string;
daytime_phone?: string;
evening_phone?: string;
email: string;
street_address: string;
city: string;
language?: Language;
drivers: LeadPostDriver[]; // PII only — no vehicles on post
}

type PostRequest = CallPostRequest | LeadPostRequest;

// ── Post Responses ──

interface CallAcceptedResponse {
status: "ok";
}

interface LeadAcceptedResponse {
status: "accepted";
bid_id: string;
external_id?: string;
warnings?: string[];
}

interface RejectedResponse {
status: "rejected";
reason: "invalid_bid" | "bid_expired" | "duplicate_phone" | "duplicate_call" | "missing_consent_proof" | "failed_secondary_evaluation";
}

type PostResponse = CallAcceptedResponse | LeadAcceptedResponse | RejectedResponse;

// ── Status Responses ──

interface LeadStatusResponse {
bid_id: string;
external_id?: string;
status: "accepted" | "rejected";
billable: boolean;
created_at: string;
}

interface CallStatusResponse {
bid_id: string;
external_id?: string;
status: "accepted" | "rejected";
billable: boolean;
call_duration: number | null;
minimum_call_duration: number;
created_at: string;
}

// ── Error Response ──

interface ErrorResponse {
status: "error";
errors: string[]; // Always an array, even for single errors
}

// ── Enums ──

type MaritalStatus = "single" | "married" | "divorced" | "separated" | "widowed" | "domestic_partnership" | "civil_union";
type CreditStatus = "excellent" | "above_average" | "average" | "below_average" | "poor";
type ResidenceType = "single_family_home" | "townhouse" | "condo" | "apartment" | "mobile_home" | "other";
type ViolationType = "driving_under_the_influence" | "speeding" | "driving_while_using_a_cell_phone" | "other";
type Education = "less_than_high_school" | "vocational" | "high_school" | "high_school_pursuing_bachelors" | "associate" | "associate_pursuing_bachelors" | "bachelors" | "bachelors_pursuing_graduate" | "masters" | "doctors" | "lawyer" | "phd";
type EmploymentStatus = "company" | "self" | "military" | "government" | "retired" | "student" | "homemaker" | "unemployed";
type Industry = "financial" | "agriculture" | "arts" | "assistants" | "automotive" | "cleaning" | "computers" | "construction" | "counseling" | "education" | "engineering" | "executives" | "health" | "law" | "operators" | "postal" | "maintenance" | "service" | "food" | "sales" | "science" | "travel";
type StudentType = "high_school_student" | "technical_vocational_student" | "freshman_undergraduate" | "sophomore_undergraduate" | "junior_undergraduate" | "senior_undergraduate" | "graduate_student" | "law_student" | "medical_student";
type MilitaryAffiliation = "active_duty" | "military_retiree" | "veteran" | "military_academy_cadet" | "national_guard" | "military_reserves";
type CommercialUseType =
// Delivery
| "courier_service" | "delivery_fast_food" | "delivery_retail_wholesale"
| "delivery_route" | "delivery_us_mail" | "delivery_and_sales"
// Sales & client visits
| "sales_multistate" | "sales_route" | "sales_calls"
| "visit_clients" | "visit_outside_offices"
// Professional
| "doctor_professional" | "lawyer_professional" | "clergy" | "social_worker"
// Travel & transport
| "travel_to_jobsites" | "travel_to_meetings" | "transport_people" | "ridesharing"
// Other
| "daycare" | "farm_use" | "real_estate" | "repair_installation";
type CurrentCompany = "21stCentury" | "AAA" | "Allstate" | "AmFam" | "Amica" | "AssuranceAmerica" | "BristolWest" | "Dairyland" | "DirectAuto" | "Elephant" | "Erie" | "Esurance" | "Farmers" | "Gainsco" | "Geico" | "Hartford" | "Infinity" | "Kemper" | "LibertyMutual" | "Mercury" | "MetLife" | "NationalGeneral" | "Nationwide" | "Progressive" | "Root" | "SafeAuto" | "Safeco" | "StateFarm" | "TheGeneral" | "Travelers" | "USAA" | "other";

Changelog

DateChange
2026-08-05Post timeout raised to 45s; ping to 3s. The post verifies consent proof with your provider and retries once, so the previous 15s was shorter than the work the endpoint does. A retry during verification returns 202 processing — treat it as "keep waiting", never as a rejection
2026-08-05Added safebind_cert_urlSafeBind proof of consent, accepted in place of trusted_form_cert_url. Send one or the other; if both arrive we verify the TrustedForm certificate. missing_consent_proof now fires only when neither is present
2026-08-04Added source attribution to lead pings: source_type ("direct" / "affiliate"), affiliate_name, affiliate_id
2026-08-04sub_id deprecated — use affiliate_id to identify a business. sub_id is still accepted and still works for its original purpose (an opaque path, creative, or placement token within one source), but it should no longer be used to identify who generated the lead
2026-04-07Added lead_type field ("exclusive" / "shared") to lead pings — defaults to "shared" if omitted. Exclusive leads receive higher bids
2026-04-07Documented current_policy_expires date range: must be today or later, no more than 1 year in the future
2026-03-23Initial release