API reference
POSTRecord an approval made outside Light AI
POST /v1/quotes/{quote_id}/mark_approved: For a homeowner who approved in your CRM or field app (e.g.
POST
https://api.light-ai.app/v1/quotes/{quote_id}/mark_approvedFor a homeowner who approved in your CRM or field app (e.g. Jobber). Works on the current published revision while it is sent; 409 quote_already_decided if the homeowner already decided in Light AI and 409 quote_not_current for an older revision. A different selection creates the approved quote with its own id (replaces_quote_id). Emits quote.approved. Test keys run the same contract on the quotes test mode created (publish one with POST /v1/test_helpers/leads/{lead_id}/publish_quote); events reach test endpoints only.
| Scope | quotes:write |
|---|---|
| Credential | API key: Authorization: Bearer lai_live_… (or lai_test_…). Partner keys add Light-AI-Organization. |
| Test mode | Works with test keys against simulated test objects. |
| Idempotency | Idempotency-Key supported. See Idempotency. |
Path parameters#
The quote id (
qt_…). Id prefix qt_.Headers#
Partner keys: the connected organization to act for (
org_…). Organization keys may omit it. Id prefix org_.Pin a dated API version (defaults to the key's version). One of:
2026-11-01.Retry-safe writes: the same key and body replay the first response for 24 hours (
Idempotent-Replayed: true). 8–128 characters.Request body#
RFC 3339 date-time with offset.
Defaults to the published selection. 1–300 items.
The approval's id in your system (e.g. the Jobber quote id).
Lowercase system name, e.g.
jobber, hubspot, highlevel.Optional sub-namespace in that system (e.g. an account id). null or "": none. At most 120 characters.
1–255 characters.
At most 120 characters.
At most 1000 characters.
The system that caused this write (shown as origin.source_system on the resulting events, for loop prevention).
Response 200#
The approved quote.
Id prefix
qt_.Always
quote.false for objects created with a test key.
One of:
sent, approved, declined, expired, superseded, disabled.Id prefix
qt_.Id prefix
qt_.The ids integrations wrote back on this quote (PUT /v1/quotes/{id}/external_refs/{system}).
self: written by your integration (editable); others are read-only. One of: self, organization, partner.Webhook payloads only: present (true) when the quote was too large for one delivery (256 KB). line_items is then empty; read the full quote with GET /v1/quotes/{id}. Never present on API responses. Always
true.Id prefix
prj_.One of:
request, quote_drafted, quote_sent, approved, completed, closed_lost.self: written by your integration (editable); others are read-only. One of: self, organization, partner.Id prefix
lead_.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.self: written by your integration (editable); others are read-only. One of: self, organization, partner.Contact details need clients:read; otherwise only the id.
Id prefix
cli_.E.164, or null when the stored number could not be normalized (see phone_raw).
The number as stored in Light AI.
self: written by your integration (editable); others are read-only. One of: self, organization, partner.Id prefix
prop_.Always present. Components may be null for addresses entered as one line.
Id prefix
rev_.RFC 3339 timestamp (UTC, milliseconds).
One of:
light-ai, external, view-only.Always
USD.The line item id in the published document.
Stable catalog code (template line item); null for custom lines.
The measured quantity (decimal for feet and square feet, whole for counted items); null for an unmeasured line.
One of:
ft, sqft, each, set.Set when quantity × unit_price equals amount to the cent; otherwise null (e.g. mini lights priced by the set) and amount is the line total.
One of:
c9, mini.One of:
percentage, fixed.The discount line's name on the customer quote, e.g. "Black Friday offer" (a website request's offer); absent for a plain discount.
The homeowner's quote link. Only with the opt-in quotes:share_link scope; every read is audited.
Present only when a PDF is bound to this exact revision.
The image's title as published on the quote. For a mockup: the image's own title, else its mockup card's name (cards are named from their light colors, for example "Warm White" or "Candy Cane", unless the contractor renamed them).
null (ids only) unless the credential or endpoint holds quotes:asset_links: then a durable link that works while the customer link is active (at most 30 days); null for an image no longer stored.
The image's title as published on the quote. For a mockup: the image's own title, else its mockup card's name (cards are named from their light colors, for example "Warm White" or "Candy Cane", unless the contractor renamed them).
null (ids only) unless the credential or endpoint holds quotes:asset_links: then a durable link that works while the customer link is active (at most 30 days); null for an image no longer stored.
RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
One of:
light_ai, legacy_portal, external.RFC 3339 timestamp (UTC, milliseconds).
Always
external.RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
RFC 3339 timestamp (UTC, milliseconds).
Always
true.Errors#
| Status | Meaning |
|---|---|
400 | Bad request. bad_request, test_mode_mismatch, test_mode_unsupported, unsupported_api_version |
401 | Missing or invalid credential. authentication_required, invalid_api_key, api_key_revoked, api_key_expired, api_key_suspended |
403 | Not allowed. insufficient_scope, plan_upgrade_required, organization_paused, organization_off_season, api_disabled, api_access_not_enabled, ip_not_allowed, forbidden |
404 | Not found (also for objects of other organizations and for organizations without an active grant). |
409 | Conflict. stale_state, conflict, idempotency_in_progress, idempotency_scope_mismatch, quote_already_decided, quote_not_current, billing_review_required |
413 | Request bodies are limited to 256 KB. |
415 | The body is not JSON: send Content-Type: application/json. unsupported_media_type |
422 | Validation failed. idempotency_key_reused |
429 | Rate limited or daily cap reached. See Retry-After and the RateLimit headers. rate_limited, daily_cap_reached |
500 | Unexpected server error. Safe to retry with exponential backoff; quote the Request-Id to support. internal_error |
503 | Temporarily unavailable. Retry with exponential backoff. service_unavailable |