Light AIDocs Beta Open Light AI

API reference

GETList leads

GET /v1/leads: Newest first by default.

GEThttps://api.light-ai.app/v1/leads

Newest first by default. For an incremental sync use sort=updated_asc&updated_since=…, follow next_cursor, and start the next poll a few minutes before your last updated_at (de-duplicate by id): a late commit can carry an earlier timestamp. For a gapless change feed poll GET /v1/events?sort=sync_asc.

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.

Query parameters#

limitinteger#
Page size, 1–100 (default 25). 1–100.
cursorstring#
next_cursor from the previous page. At most 400 characters.
sortstring#
updated_asc pages objects in last-change order (for an initial or incremental sync with updated_since). A write that commits late can carry an earlier updated_at, so re-poll from your high-water mark minus 5 minutes and de-duplicate by id + updated_at; GET /v1/events?sort=sync_asc is the gapless change feed. One of: created_desc, updated_asc.
statusstring#
One of: new, converted, spam, archived, duplicate, held.
sourcestring#
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.
created_aftertimestamp#
Only objects created after this time.
created_beforetimestamp#
Only objects created before this time.
updated_sincetimestamp#
Only objects updated after this time (alias: updated_after). Overlap polls by a few minutes: see sort.
updated_aftertimestamp#
Alias of updated_since.
external_refstring#
<system>:<id> in your own namespace, e.g. jobber:Z2lk123. At most 400 characters.
external_ref_namespacestring#
At most 120 characters.
form_idstring#
Only leads from this website request form (form_…). Id prefix form_.

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.

Response 200#

A page of 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).
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