Light AIDocs Beta Open Light AI

API reference

POSTSearch leads by email, phone or address

POST /v1/leads/search: Lookups by personal data go in the request body, never the query string.

POSThttps://api.light-ai.app/v1/leads/search

Lookups by personal data go in the request body, never the query string. Returns at most 100 matches, newest first.

Scopeleads:read
CredentialAPI key: Authorization: Bearer lai_live_… (or lai_test_…). Partner keys add Light-AI-Organization.
Test modeWorks with test keys against simulated test objects.

Headers#

Light-AI-Organizationstring#
Partner keys: the connected organization to act for (org_…). Organization keys may omit it. Id prefix org_.
Light-AI-Versionstring#
Pin a dated API version (defaults to the key's version). One of: 2026-11-01.

Request body#

emailstring | null#
An email address ("" or null: absent). At most 254 characters.
phonestring | null#
At most 40 characters.
addressobject | null#
address.line1string | null#
At most 200 characters.
address.line2string | null#
At most 200 characters.
address.citystring | null#
At most 120 characters.
address.regionstring | null#
State or region, e.g. TX. At most 120 characters.
address.postal_codestring | null#
At most 20 characters.
address.countrystring | null#
ISO 3166-1 alpha-2 in any case, or a common name ("USA", "United States"); normalized to alpha-2. Defaults to US. At most 60 characters.
address.formattedstring | null#
A one-line address, when components are not available. At most 500 characters.
address.latnumber | null#
-90–90.
address.lngnumber | null#
-180–180.
address.place_idstring | null#
Google place id, when known (best dedupe key). At most 300 characters.

Response 200#

Matching leads.

objectstringrequired#
Always list.
dataobject[]required#
data[].idstringrequired#
Id prefix lead_.
data[].objectstringrequired#
Always lead.
data[].livemodebooleanrequired#
false for objects created with a test key.
data[].statusstringrequired#
One of: new, converted, spam, archived, duplicate.
data[].heldbooleanrequired#
Accepted while the organization was paused; converts automatically on resume.
data[].needs_addressbooleanrequired#
A lead without a street address (none, or only a ZIP, city or region) waits for one (PATCH /v1/leads/{id} with address).
data[].sourcestringrequired#
One of: website_widget, api, google_ads_lead_form, google_lsa, meta_lead_ad, highlevel, hubspot, jobber, service_autopilot, realgreen, sweepandgo, deeplawn, zapier, n8n, make, crm_import, other, manual.
data[].source_detailstring | nullrequired#
data[].contactobjectrequired#
data[].contact.first_namestring | nullrequired#
data[].contact.last_namestring | nullrequired#
data[].contact.emailstring | nullrequired#
data[].contact.email_rawstring | null#
The address as received when it is not a valid email (the lead was kept because its phone works).
data[].contact.phonestring | nullrequired#
E.164, or null when the number could not be normalized (see phone_raw).
data[].contact.phone_rawstring | nullrequired#
The number as received (extensions, local or foreign formats are kept here).
data[].contact.phone_verifiedboolean | nullrequired#
data[].addressobject | nullrequired#
data[].address.line1string | nullrequired#
data[].address.line2string | nullrequired#
data[].address.citystring | nullrequired#
data[].address.regionstring | nullrequired#
data[].address.postal_codestring | nullrequired#
data[].address.countrystring | nullrequired#
data[].address.formattedstringrequired#
Always present. Components may be null for addresses entered as one line.
data[].address.latnumber | nullrequired#
data[].address.lngnumber | nullrequired#
data[].service_intereststring | nullrequired#
One of: christmas_lights, permanent_lighting, landscape_lighting, custom.
data[].service_interest_rawstring | nullrequired#
data[].messagestring | nullrequired#
data[].attributionobjectrequired#
data[].custom_fieldsobjectrequired#
data[].notesstring | nullrequired#
data[].status_reasonstring | nullrequired#
data[].client_idstring | nullrequired#
Id prefix cli_.
data[].property_idstring | nullrequired#
Id prefix prop_.
data[].possible_client_matchobject | nullrequired#
An existing client with the same email or phone that this integration was not allowed to update.
data[].possible_client_match.client_idstringrequired#
Id prefix cli_.
data[].projectobject | nullrequired#
data[].project.idstringrequired#
Id prefix prj_.
data[].project.statusstringrequired#
One of: intake, measuring, mockup, quote, approved, completed, archived.
data[].project.pipeline_stagestring | nullrequired#
One of: request, quote_drafted, quote_sent, approved, completed, closed_lost.
data[].project.app_urlstring | nullrequired#
data[].formobject | null#
The website request form a website_widget lead came from (null for other sources).
data[].form.idstringrequired#
Id prefix form_.
data[].form.namestringrequired#
data[].mockup_statusstring#
Instant preview of a website lead: not_requested (the form has no preview), pending (generating), ready (at least one image), failed, or skipped (a fallback applied: low credits, a cap, Off-Season, pause, plan). lead.mockup_ready fires once when it leaves pending. One of: not_requested, pending, ready, failed, skipped.
data[].form_answersobject[]#
Every website request form answer in the form's question order, with its question, group, raw value (the same value as custom_fields[key]) and display labels. Branch questions appear only when they were shown (e.g. tree_count only when trees_bushes is true). Answers to questions the form no longer has come last. An empty array for other sources.
data[].form_answers[].keystringrequired#
Stable answer key, the same key as in custom_fields (e.g. surfaces, primary_color, tree_count, custom_…).
data[].form_answers[].questionstringrequired#
The question as the form asked it (the key when the form no longer has that question).
data[].form_answers[].groupstringrequired#
Answer group: lighting, trees (trees & bushes), decor (wreaths & garland), details or custom (the contractor's own questions). More groups may be added.
data[].form_answers[].typestring | nullrequired#
Question type (null when the form no longer has the question). One of: color, choice, multi_choice, toggle, text.
data[].form_answers[].valueanyrequired#
The raw answer, identical to custom_fields[key]: a choice value, an array of values, true/false or text.
data[].form_answers[].labelsstring[]required#
Display labels of the answer in order (toggles: Yes or No; text: the text).
data[].preview_looksstring[]#
The look (light color value, e.g. warm_white) of the request's instant preview: an array with at most one item (one AI mockup per request; leads sent before October 2026 may list up to 3, in order). Empty when there is no preview.
data[].offerobject | null#
The website request form's offer (for example "Black Friday: 20% off installs") that was showing when the homeowner sent the request, kept as it was then; null when no offer was active and for other sources. label is "20% off", "$150 off" or empty (a headline-only offer). When the request becomes a project, the quote builder pre-fills the quote discount from it once (the contractor can change or remove it); the published quote shows it as discount with a label such as "Black Friday offer".
data[].offer.templatestring | null#
The Light AI campaign template that filled the offer; null or absent for the contractor's own. One of: classic, early_bird, black_friday, last_chance.
data[].offer.headlinestringrequired#
data[].offer.discount_typestringrequired#
One of: percent, amount.
data[].offer.discount_percentinteger | nullrequired#
data[].offer.discount_amountinteger | nullrequired#
data[].offer.labelstringrequired#
data[].offer.ends_atstring | nullrequired#
data[].offer.book_bystring | nullrequired#
data[].offer.captured_atstring | nullrequired#
data[].assetsobject#
The homeowner's photo and the instant preview images (each with its look). GET, list and search answers carry 60-minute signed URLs; webhook payloads and the events feed carry the ids with url null (fetch the lead for fresh links).
data[].assets.photosobject[]required#
data[].assets.mockupsobject[]required#
data[].external_refsobject[]required#
data[].external_refs[].systemstringrequired#
data[].external_refs[].namespacestring | nullrequired#
data[].external_refs[].idstringrequired#
data[].external_refs[].ownerstringrequired#
self: written by your integration (editable); others are read-only. One of: self, organization, partner.
data[].external_refs[].read_onlybooleanrequired#
data[].touch_countintegerrequired#
data[].last_touched_attimestamp | nullrequired#
RFC 3339 timestamp (UTC, milliseconds).
data[].submitted_attimestamprequired#
RFC 3339 timestamp (UTC, milliseconds).
data[].created_attimestamprequired#
RFC 3339 timestamp (UTC, milliseconds).
data[].updated_attimestamprequired#
RFC 3339 timestamp (UTC, milliseconds).
data[].replayedboolean#
Present when an external_ref replay returned an existing lead. Always true.
data[].fixtureboolean#
Test-mode fixture object. Always true.
has_morebooleanrequired#
next_cursorstring | nullrequired#
Pass as cursor to fetch the next page.

Errors#

StatusMeaning
400Bad request. bad_request, test_mode_mismatch, test_mode_unsupported, unsupported_api_version
401Missing or invalid credential. authentication_required, invalid_api_key, api_key_revoked, api_key_expired, api_key_suspended
403Not allowed. insufficient_scope, plan_upgrade_required, organization_paused, organization_off_season, api_disabled, api_access_not_enabled, ip_not_allowed, forbidden
404Not found (also for objects of other organizations and for organizations without an active grant).
409Conflict. stale_state, conflict, idempotency_in_progress, idempotency_scope_mismatch, quote_already_decided, quote_not_current, billing_review_required
413Request bodies are limited to 256 KB.
415The body is not JSON: send Content-Type: application/json. unsupported_media_type
422Validation failed. idempotency_key_reused
429Rate limited or daily cap reached. See Retry-After and the RateLimit headers. rate_limited, daily_cap_reached
500Unexpected server error. Safe to retry with exponential backoff; quote the Request-Id to support. internal_error
503Temporarily unavailable. Retry with exponential backoff. service_unavailable