---
title: "External references"
url: https://docs.light-ai.app/external-refs.md
canonical_url: https://docs.light-ai.app/external-refs
last_updated: 2026-10-13
summary: "Write your CRM ids back onto Light AI objects, look objects up by them, and keep two-way syncs loop-free."
api_status: beta
related:
  - https://docs.light-ai.app/pagination
  - https://docs.light-ai.app/webhooks
  - https://docs.light-ai.app/integrations
---

# External references

> Write your CRM ids back onto Light AI objects, look objects up by them, and keep two-way syncs loop-free.

An external reference links a Light AI object to its id in your system. Leads, clients, projects and quotes carry them in `external_refs`.

curl:

```bash
curl -X PUT "https://api.light-ai.app/v1/projects/prj_…/external_refs/hubspot" \
  -H "Authorization: Bearer $LIGHT_AI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "deal-9981"
}'
```

JavaScript:

```javascript
const response = await fetch('https://api.light-ai.app/v1/projects/prj_…/external_refs/hubspot', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_AI_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "id": "deal-9981"
  }),
});
const data = await response.json();
```

Python:

```python
import os, requests

response = requests.put(
    "https://api.light-ai.app/v1/projects/prj_…/external_refs/hubspot",
    headers={
        "Authorization": f"Bearer {os.environ['LIGHT_AI_API_KEY']}",
    },
    json={
        "id": "deal-9981"
    },
)
data = response.json()
```

- `system` is a lowercase name (`jobber`, `hubspot`, `highlevel`); `namespace` is optional (an account or location id).
- References are namespaced per integration: you can never overwrite another integration's ids.
- Look up by reference on any list: `GET /v1/leads?external_ref=jobber:Z2lk123`.
- On `POST /v1/leads`, an `external_ref` you already sent replays the original lead; `POST /v1/clients` matches your reference first, then e-mail, then phone.

## Key records on the project

Every published revision of a quote is a new quote id. Key your deal or job on `project.id` (or its external reference) and update it as quotes are published, approved, declined or expire.

## Outcomes from your system

When the homeowner approves or declines in your CRM or field app, record it on the current quote. Light AI moves the project and emits the same events.

curl:

```bash
curl -X POST "https://api.light-ai.app/v1/quotes/qt_…/mark_approved" \
  -H "Authorization: Bearer $LIGHT_AI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "approved_at": "2026-10-12T16:00:00Z",
  "signed_by": "Dana Reyes",
  "external_ref": {
    "system": "jobber",
    "id": "Z2lkOi8vSm9iYmVyL1F1b3RlLzEyMw"
  },
  "source_system": "jobber"
}'
```

JavaScript:

```javascript
const response = await fetch('https://api.light-ai.app/v1/quotes/qt_…/mark_approved', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_AI_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "approved_at": "2026-10-12T16:00:00Z",
    "signed_by": "Dana Reyes",
    "external_ref": {
      "system": "jobber",
      "id": "Z2lkOi8vSm9iYmVyL1F1b3RlLzEyMw"
    },
    "source_system": "jobber"
  }),
});
const data = await response.json();
```

Python:

```python
import os, uuid, requests

response = requests.post(
    "https://api.light-ai.app/v1/quotes/qt_…/mark_approved",
    headers={
        "Authorization": f"Bearer {os.environ['LIGHT_AI_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "approved_at": "2026-10-12T16:00:00Z",
        "signed_by": "Dana Reyes",
        "external_ref": {
            "system": "jobber",
            "id": "Z2lkOi8vSm9iYmVyL1F1b3RlLzEyMw"
        },
        "source_system": "jobber"
    },
)
data = response.json()
```

409 `quote_already_decided` means the homeowner already decided in Light AI; 409 `quote_not_current` means a newer revision exists.

## Avoiding loops

Send `source_system` (your system name) on writes. Events caused by them carry `origin.source_system`; skip those in your listener, or set `exclude_own_origin: true` on your endpoint.
