# Tracerfy API Documentation

Complete API reference for Tracerfy's skip tracing and DNC scrubbing platform.

**Base URL:** `https://tracerfy.com`

**Authentication:** All endpoints require a Bearer token in the `Authorization` header.

---

## Table of Contents

- [Fetch all Queue](#queues)
- [Fetch Single Queue](#queue)
- [Analytics](#analytics)
- [Batch Trace](#trace)
- [Instant Trace Lookup](#instant-trace)
- [APN Batch Trace](#apn-batch)
- [APN Instant Lookup](#apn-lookup)
- [Trace Webhooks](#trace-webhooks)
- [Start DNC Scrub](#dnc-scrub)
- [DNC Scrub from Trace](#dnc-scrub-from-queue)
- [Fetch DNC Queue](#dnc-queue)
- [DNC Webhooks](#dnc-webhooks)
- [List Filters](#lead-builder-filters-endpoint)
- [Preview Lead List](#lead-builder-preview)
- [Execute Lead Build](#lead-builder-execute)
- [Check Build Status](#lead-builder-status)
- [Lead List Rows (JSON)](#lead-builder-rows)
- [Single-Address Lookup](#lead-builder-lookup)
- [Lead Builder Webhooks](#lead-builder-webhooks)
- [AI Assist (TraceAI)](#lead-builder-ai-assist)
- [Filter Reference](#lead-builder-filters)

---

## GET Fetch all Queue

`GET /v1/api/queues/`

Returns all queues for the authenticated user. Each queue represents a trace job created via API or the app. While a queue is pending, the serializer hides rows_uploaded and credits_deducted for API queues. When complete, download_url is populated with a public CSV link.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Example Request

```bash
curl -X GET 'https://tracerfy.com/v1/api/queues/' -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
[
  {
    "id": 123,
    "created_at": "2025-01-01T12:00:00Z",
    "pending": false,
    "download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/9a584124-77c2-4612-b8e9-f9efe6fbdc3d.csv",
    "rows_uploaded": 2500,
    "credits_deducted": 2500,
    "queue_type": "api",
    "trace_type": "normal",
    "credits_per_lead": 1
  }
]
```

---

## GET Fetch Single Queue

`GET /v1/api/queue/:id`

Returns the property records associated with a queue's posted addresses. Object-level permission enforced: only the queue owner can access. Null contact fields are normalized to empty strings in the response. 

**Response varies based on trace_type:**
• **Normal Trace** (trace_type='normal'): Returns basic property contact data (phones and emails)
• **Advanced Trace** (trace_type='advanced'): Finds the property owner and returns their contact data (name, phones, emails and mailing address)
• **Custom Trace** (trace_type='custom'): Returns basic property contact data (phones, emails, mailing address)

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `:id` | path | `integer` | Yes | Queue ID |

### Example Request

```bash
curl -X GET 'https://tracerfy.com/v1/api/queue/123' -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
// Normal Trace Response (trace_type='normal')
[
  {
    "address": "123 Main St",
    "city": "Austin",
    "state": "TX",
    "mail_address": "PO Box 111",
    "mail_city": "Austin",
    "mail_state": "TX",
    "first_name": "Jane",
    "last_name": "Doe",
    "primary_phone": "5125550100",
    "primary_phone_type": "Mobile",
    "email_1": "jane@example.com",
    "email_2": "",
    "email_3": "",
    "email_4": "",
    "email_5": "",
    "mobile_1": "5125550100",
    "mobile_2": "",
    "mobile_3": "",
    "mobile_4": "",
    "mobile_5": "",
    "landline_1": "",
    "landline_2": "",
    "landline_3": ""
  }
]
```

---

## GET Analytics

`GET /v1/api/analytics/`

Aggregated summary for your account: total_queues, properties_traced (sum of posted addresses per queue), queues_pending, queues_completed, and current credit balance.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Example Request

```bash
curl -X GET 'https://tracerfy.com/v1/api/analytics/' -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
{
  "total_queues": 12,
  "properties_traced": 18350,
  "queues_pending": 2,
  "queues_completed": 10,
  "balance": 940
}
```

---

## POST Batch Trace

`POST /v1/api/trace/`

Asynchronous batch endpoint for processing multiple addresses at once via CSV or JSON. Specify trace_type='normal' (1 credit/lead) or 'advanced' (2 credits/lead). Cleans and de-duplicates rows, then enqueues processing in the background. If credits are insufficient the request is rejected. Returns a queue_id immediately along with `estimated_wait_seconds` (estimated processing time in seconds); results are delivered via download_url when complete. For single-address real-time lookups, use the Instant Trace Lookup endpoint instead.

⚠️ API Usage Policy: Do not abuse API POST calls. Accounts found to be abusing the API will be put on hold. Maximum rate limit is 10 POST trace requests per 5-minute window. Please use the API responsibly and in accordance with our Terms of Service - API Rate Limits &amp; Abuse Policy.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `multipart/form-data or application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `address_column` | body | `string` | Yes | Column for property address |
| `city_column` | body | `string` | Yes | Column for property city |
| `state_column` | body | `string` | Yes | Column for property state |
| `zip_column` | body | `string` | No | Property ZIP (**optional — for advanced traces**, we return this from our data if not provided) |
| `first_name_column` | body | `string` | Yes | Owner first name (**optional for advanced traces** — we identify the owner for you) |
| `last_name_column` | body | `string` | Yes | Owner last name (**optional for advanced traces** — we identify the owner for you) |
| `mail_address_column` | body | `string` | Yes | Mailing address (**optional for advanced traces** — we return this from our data) |
| `mail_city_column` | body | `string` | Yes | Mailing city (**optional for advanced traces** — we return this from our data) |
| `mail_state_column` | body | `string` | Yes | Mailing state (**optional for advanced traces** — we return this from our data) |
| `mailing_zip_column` | body | `string` | No | Mailing ZIP (**optional — for advanced traces**, we return this from our data if not provided) |
| `trace_type` | body | `string` | No | Trace type: 'normal' (1 credit/lead) or 'advanced' (2 credits/lead). Defaults to 'normal'. For advanced traces, only address_column, city_column, and state_column are required — all other fields are optional as we identify the owner and return their full contact and mailing info. |
| `csv_file` | form-data | `file` | Yes | CSV file of records |
| `json_data` | body | `string` | No | Raw JSON array of records (alternative to csv_file) |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/trace/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -F 'csv_file=@/path/to/records.csv' \
  -F 'address_column=address' \
  -F 'city_column=city' \
  -F 'state_column=state' \
  -F 'zip_column=zip' \
  -F 'first_name_column=first_name' \
  -F 'last_name_column=last_name' \
  -F 'mail_address_column=mail_address' \
  -F 'mail_city_column=mail_city' \
  -F 'mail_state_column=mail_state' \
  -F 'mailing_zip_column=mailing_zip' \
  -F 'trace_type=normal'
```

### Example Response (200)

```json
{
  "message": "Queue created",
  "queue_id": 456,
  "status": "pending",
  "created_at": "2025-01-02T10:15:00Z",
  "rows_uploaded": 100,
  "trace_type": "normal",
  "credits_per_lead": 1,
  "estimated_wait_seconds": 30
}
```

---

## POST Instant Trace Lookup

`POST /v1/api/trace/lookup/`

Synchronous single-address skip trace. Returns responses immediately as JSON — no queue and no CSV. Ideal for one-off lookups or integrating skip trace data into your own UI at scale.

**5 credits per hit, 0 credits on miss.** Rate limited to 500 RPM per user.

**Two lookup modes:**
• **find_owner: true** (default) — send only address/city/state, returns the property owner(s) and their contact info
• **find_owner: false** — include first_name + last_name to search for a specific person at the address

**Response includes per person:** name, age, DOB, deceased flag, property owner flag, litigator flag, mailing address, all phones (with DNC status, carrier, type, rank), and all emails.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `address` | body | `string` | Yes | Property street address |
| `city` | body | `string` | Yes | Property city |
| `state` | body | `string` | Yes | Property state (2-letter abbreviation) |
| `zip` | body | `string` | No | Property ZIP code. Optional but **strongly recommended** — without it, results may match a different property at a similar address in the same city. |
| `find_owner` | body | `boolean` | No | `true` (default) — find property owner, no name needed. `false` — find a specific person at the address, requires first_name + last_name. |
| `first_name` | body | `string` | No | Person's first name. **Required when find_owner is false.** |
| `last_name` | body | `string` | No | Person's last name. **Required when find_owner is false.** |

### Example Request

```bash
# Owner lookup (find property owner)
curl -X POST 'https://tracerfy.com/v1/api/trace/lookup/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"address": "123 Main St", "city": "Austin", "state": "TX", "zip": "78701", "find_owner": true}'

# Person lookup (find specific person at address)
curl -X POST 'https://tracerfy.com/v1/api/trace/lookup/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"address": "123 Main St", "city": "Austin", "state": "TX", "zip": "78701", "find_owner": false, "first_name": "Jane", "last_name": "Doe"}'
```

### Example Response (200)

```json
// Owner lookup hit (find_owner: true) — 5 credits deducted
{
  "address": "123 Main St",
  "city": "Austin",
  "state": "TX",
  "zip": "78701",
  "find_owner": true,
  "hit": true,
  "persons_count": 1,
  "credits_deducted": 5,
  "persons": [
    {
      "first_name": "Jane",
      "last_name": "Doe",
      "full_name": "Jane Doe",
      "dob": "1985-03-22",
      "age": "41",
      "deceased": false,
      "property_owner": true,
      "litigator": false,
      "mailing_address": {
        "street": "PO Box 111",
        "city": "Austin",
        "state": "TX",
        "zip": "78702"
      },
      "phones": [
        {
          "number": "5125550100",
          "type": "Mobile",
          "dnc": false,
          "carrier": "T-MOBILE USA INC.",
          "rank": 1
        },
        {
          "number": "5125550200",
          "type": "Landline",
          "dnc": true,
          "carrier": "AT&T TEXAS",
          "rank": 2
        }
      ],
      "emails": [
        {
          "email": "jane.doe@example.com",
          "rank": 1
        }
      ]
    }
  ]
}

// Person lookup hit (find_owner: false) — 5 credits deducted
{
  "address": "123 Main St",
  "city": "Austin",
  "state": "TX",
  "zip": "78701",
  "find_owner": false,
  "hit": true,
  "persons_count": 1,
  "credits_deducted": 5,
  "persons": [
    {
      "first_name": "John",
      "last_name": "Smith",
      "full_name": "John Smith",
      "dob": "1978-11-03",
      "age": "47",
      "deceased": false,
      "property_owner": false,
      "litigator": false,
      "mailing_address": {
        "street": "456 Oak Ave",
        "city": "Dallas",
        "state": "TX",
        "zip": "75201"
      },
      "phones": [
        {
          "number": "2145550300",
          "type": "Mobile",
          "dnc": false,
          "carrier": "VERIZON WIRELESS",
          "rank": 1
        }
      ],
      "emails": [
        {
          "email": "john.smith@example.com",
          "rank": 1
        }
      ]
    }
  ]
}

// Miss — no results found, 0 credits deducted
{
  "address": "999 Nowhere Blvd",
  "city": "Austin",
  "state": "TX",
  "zip": "78701",
  "find_owner": true,
  "hit": false,
  "persons_count": 0,
  "credits_deducted": 0,
  "persons": []
}
```

---

## POST APN Batch Trace

`POST /v1/api/trace/parcel/`

Submit a batch of parcel IDs (APNs) for skip tracing. Each parcel is looked up to find the property owner's contact information — name, mailing address, phones with DNC flags and carrier, and emails.

**5 credits per hit, 0 on miss.** Rows with no match still appear in the CSV with empty contact columns.

Results are delivered asynchronously via a CSV download URL. Poll the queue endpoint `GET /v1/api/trace/parcel/queue/:id` for status, or set a webhook URL in your account to get notified on completion.

**APN format:** the `#` prefix is optional and will be stripped automatically. Parcel IDs are formatted internally to match the standard APN format for each county.

**Rate limit:** 10 batch submissions per 5 minutes.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `multipart/form-data` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `csv_file` | form-data | `file` | Yes | CSV file with parcel ID, county, and state columns. |
| `parcel_id_column` | body | `string` | Yes | Name of the column containing parcel IDs. |
| `county_column` | body | `string` | Yes | Name of the column containing county names. |
| `state_column` | body | `string` | Yes | Name of the column containing state abbreviations (e.g. FL, TX). |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/trace/parcel/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -F 'csv_file=@parcels.csv' \
  -F 'parcel_id_column=parcel_id' \
  -F 'county_column=county' \
  -F 'state_column=state'
```

### Example Response (200)

```json
{
  "message": "Parcel trace started",
  "parcel_queue_id": 42,
  "created_at": "2026-04-07T12:00:00Z",
  "status": "pending",
  "rows_uploaded": 500,
  "credits_per_parcel": 5
}
```

---

## POST APN Instant Lookup

`POST /v1/api/trace/parcel/lookup/`

Synchronous single-parcel skip trace. Returns owner contact info immediately as JSON — no queue, no CSV, no polling. Use this when you have one APN and want instant results.

**5 credits per hit, 0 on miss.**

The response includes every field the batch CSV delivers: owner name, property address, mailing address, all phones with DNC flag and carrier, emails, plus `property_owner`, `deceased`, `litigator`, and `age`.

**APN format:** the `#` prefix is optional.

**Rate limit:** 500 requests per minute per user.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `parcel_id` | body | `string` | Yes | The parcel ID (APN). The '#' prefix is optional and will be stripped automatically. |
| `county` | body | `string` | Yes | County name (e.g. 'Palm Beach'). |
| `state` | body | `string` | Yes | State abbreviation (e.g. 'FL'). |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/trace/parcel/lookup/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"parcel_id": "#00424109000007550", "county": "Palm Beach", "state": "FL"}'
```

### Example Response (200)

```json
// Hit — 5 credits deducted
{
  "parcel_id": "#00424109000007550",
  "county": "Palm Beach",
  "state": "FL",
  "hit": true,
  "persons_count": 1,
  "credits_deducted": 5,
  "persons": [
    {
      "first_name": "John",
      "last_name": "Smith",
      "full_name": "John Smith",
      "dob": "1975-03-15",
      "age": 51,
      "deceased": false,
      "property_owner": true,
      "litigator": false,
      "mailing_address": {
        "street": "456 Oak Ave",
        "city": "West Palm Beach",
        "state": "FL",
        "zip": "33401"
      },
      "phones": [
        {
          "number": "5615550100",
          "type": "Mobile",
          "dnc": false,
          "carrier": "T-MOBILE USA INC.",
          "rank": 1
        },
        {
          "number": "5615550200",
          "type": "Landline",
          "dnc": true,
          "carrier": "BELLSOUTH TELECOMM INC",
          "rank": 2
        }
      ],
      "emails": [
        {
          "email": "jsmith@example.com",
          "rank": 1
        },
        {
          "email": "john.smith@example.net",
          "rank": 2
        }
      ]
    }
  ]
}

// Miss — 0 credits
{
  "parcel_id": "#00404033000001190",
  "county": "Palm Beach",
  "state": "FL",
  "hit": false,
  "persons_count": 0,
  "credits_deducted": 0,
  "persons": []
}
```

---

## POST Trace Webhooks

`POST Account.webhook_url`

When a batch skip trace queue completes, Tracerfy POSTs the result to the webhook URL configured in your account profile. This is per-user and dynamic; no registration endpoint is required.

### Headers

| Name | Value |
|------|-------|
| `Content-Type` | `application/json` |

### Example Request

```bash
Tracerfy sends this JSON to your Account.webhook_url when a trace completes.
```

### Example Response (200)

```json
{
  "id": 365,
  "created_at": "2025-07-13T18:55:02.962332Z",
  "pending": false,
  "download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/9a584124-77c2-4612-b8e9-f9efe6fbdc3d.csv",
  "rows_uploaded": 12,
  "credits_deducted": 12,
  "queue_type": "api",
  "trace_type": "normal",
  "credits_per_lead": 1
}
```

---

## POST Start DNC Scrub

`POST /v1/api/dnc/scrub/`

Submit a phone list for DNC (Do Not Call) scrubbing. Upload a CSV with one or more phone columns, or pass a JSON array of phone numbers directly. Each phone is checked against Federal DNC, State DNC, DMA, and TCPA Litigator databases. 1 credit per phone checked.

**Input options (pick one):**
• **CSV with single column**: csv_file + phone_column (string)
• **CSV with multiple columns**: csv_file + phone_columns (array) — phones are merged &amp; deduplicated
• **JSON phone list**: phones array via application/json

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `multipart/form-data or application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `csv_file` | form-data | `file` | Yes | CSV file containing phone numbers. Required for Options 1 & 2. Do not send with phones. |
| `phone_column` | body | `string` | Yes | Single column name containing phone numbers (Option 1). Internally normalized to phone_columns. Mutually exclusive with phone_columns. |
| `phone_columns` | body | `array[string]` | Yes | List of column names containing phone numbers (Option 2). Phones are merged & deduplicated. When multiple columns are used, labels are prefixed with the column name, e.g. '(Phone_1) John Doe'. Mutually exclusive with phone_column. |
| `label_column` | body | `string` | No | Single column to label each phone (e.g., name). Internally normalized to label_columns. Mutually exclusive with label_columns. |
| `label_columns` | body | `array[string]` | No | List of columns to combine as a label for each phone (e.g., ["address", "city", "state"]). Values are joined with commas. When using multiple phone_columns, labels are also prefixed with the column name. |
| `phones` | body | `array[string]` | Yes | Direct list of phone numbers via JSON body (Option 3). Do not send with csv_file. |

### Example Request

```bash
# Option 1: CSV with a single phone column
curl -X POST 'https://tracerfy.com/v1/api/dnc/scrub/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -F 'csv_file=@/path/to/phones.csv' \
  -F 'phone_column=Phone' \
  -F 'label_column=Name'

# Option 1b: CSV with multiple label columns
curl -X POST 'https://tracerfy.com/v1/api/dnc/scrub/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -F 'csv_file=@/path/to/phones.csv' \
  -F 'phone_column=Phone' \
  -F 'label_columns=["Address", "City", "State"]'

# Option 2: CSV with multiple phone columns
curl -X POST 'https://tracerfy.com/v1/api/dnc/scrub/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -F 'csv_file=@/path/to/phones.csv' \
  -F 'phone_columns=["Phone_1", "Phone_2"]' \
  -F 'label_column=Name'

# Option 3: JSON phone list (no CSV)
curl -X POST 'https://tracerfy.com/v1/api/dnc/scrub/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"phones": ["5125550100", "5125550101", "5125550102"]}'
```

### Example Response (200)

```json
{
  "message": "DNC scrub started",
  "dnc_queue_id": 5,
  "created_at": "2025-01-15T09:30:00Z",
  "status": "pending",
  "phones_to_check": 150,
  "credits_per_phone": 1
}
```

---

## POST DNC Scrub from Trace

`POST /v1/api/dnc/scrub-from-queue/`

Extract phone numbers from a completed trace queue from your skip tracing results and submit them for DNC scrubbing. Optionally specify which phone columns to include. Phones are deduplicated across all selected columns. 1 credit per phone checked.

**Valid phone_columns:** primary_phone, mobile_1, mobile_2, mobile_3, mobile_4, mobile_5, landline_1, landline_2, landline_3
If phone_columns is omitted, all 9 phone fields are included by default.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `queue_id` | body | `integer` | Yes | ID of a completed trace queue to extract phones from. |
| `phone_columns` | body | `array[string]` | No | List of phone field names to include. Defaults to all 9 phone fields. |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/dnc/scrub-from-queue/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"queue_id": 37360, "phone_columns": ["primary_phone", "mobile_1", "mobile_2"]}'
```

### Example Response (200)

```json
{
  "message": "DNC scrub started",
  "dnc_queue_id": 8,
  "created_at": "2025-01-15T10:00:00Z",
  "source_queue_id": 37360,
  "status": "pending",
  "phones_to_check": 23,
  "phone_columns_used": [
    "primary_phone",
    "mobile_1",
    "mobile_2"
  ],
  "credits_per_phone": 1
}
```

---

## GET Fetch DNC Queue

`GET /v1/api/dnc/queue/:id`

Retrieve the status and results of a DNC scrub job. When complete, two download URLs are provided: download_url (all phones with DNC flags) and clean_download_url (only phones with no DNC flags). CSV columns: phone, label, national_dnc, state_dnc, dma, litigator, phone_type, is_clean.

**Note:** While the queue is still pending, the fields `phones_checked`, `phones_clean`, and `credits_deducted` are omitted from the response. They appear once the scrub completes.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `:id` | path | `integer` | Yes | DNC Queue ID |

### Example Request

```bash
curl -X GET 'https://tracerfy.com/v1/api/dnc/queue/5' -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
{
  "id": 5,
  "created_at": "2025-01-15T09:30:00Z",
  "pending": false,
  "download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/full-results.csv",
  "clean_download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/clean-results.csv",
  "rows_uploaded": 150,
  "phones_checked": 150,
  "phones_clean": 112,
  "credits_deducted": 150,
  "source_type": "upload"
}
```

---

## POST DNC Webhooks

`POST Account.webhook_url`

When a DNC scrub completes, Tracerfy POSTs the result to the webhook URL configured in your account profile. The payload includes a `type: "dnc_scrub"` field to distinguish it from trace webhooks, plus DNC-specific fields like clean_download_url, phones_checked, and phones_clean.

### Headers

| Name | Value |
|------|-------|
| `Content-Type` | `application/json` |

### Example Request

```bash
Tracerfy sends this JSON to your Account.webhook_url when a DNC scrub completes.
```

### Example Response (200)

```json
{
  "id": 5,
  "type": "dnc_scrub",
  "created_at": "2025-01-15T09:30:00Z",
  "pending": false,
  "download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/full-results.csv",
  "clean_download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/clean-results.csv",
  "rows_uploaded": 150,
  "phones_checked": 150,
  "phones_clean": 112,
  "credits_deducted": 150,
  "source_type": "upload"
}
```

---

## GET List Filters

`GET /v1/api/lead-builder/filters/`

Returns every Lead Builder preset strategy with its human-readable label, description, and the exact filters the preset applies. Use this to enumerate available filters at runtime instead of hard-coding the list in your client. Response is static enough to cache locally for ~1 hour.

For each strategy, `default_filters` shows the filters that the preset lays down — anything you send in `filter_overrides` on `/preview/` or `/execute/` will merge on top. This lets you see exactly what `'tired_landlord'` does before you use it.

For the complete list of individual filter keys you can use in `filter_overrides`, see the Filter Reference below.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Example Request

```bash
curl 'https://tracerfy.com/v1/api/lead-builder/filters/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
{
  "strategies": [
    {
      "key": "high_equity_absentee",
      "label": "High Equity Absentee",
      "description": "Landlords with 50%+ equity who don't live in the property.",
      "default_filters": {
        "absentee_owner": true,
        "high_equity": true
      }
    },
    {
      "key": "tired_landlord",
      "label": "Tired Landlord",
      "description": "Long-hold absentees showing distress signals.",
      "default_filters": {
        "absentee_owner": true,
        "years_owned_min": 7,
        "properties_owned_min": 2
      }
    },
    {
      "key": "custom",
      "label": "Custom",
      "description": "Blank slate \u2014 configure every filter manually.",
      "default_filters": {}
    }
  ]
}

// ... 13 more preset strategies returned by this endpoint.
```

---

## POST Preview Lead List

`POST /v1/api/lead-builder/preview/`

Preview a lead list before charging. Returns the total match count, the capped count (the lower of total_matches and your requested_count), and the maximum credit cost. **Preview calls do not charge credits.**

Use this to iterate on filters and geography before committing — safe to call repeatedly. Rate limited to 500 calls per hour per user (partners exempt).

**About `max_credit_cost`:** 5 credits per row in the delivered CSV. The value is the ceiling — if fewer properties match than `requested_count`, `capped_count` drops and the charge drops with it.

**Filters:** see List Filters for preset strategies, or use `'custom'` and supply individual filters via `filter_overrides`. Full key reference at Filter Reference.

**Geography modes:** `zips`, `city`, `counties`, `states`, `radius`. See the shape examples under the `geography` param below.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `strategy` | body | `string` | No | Strategy key from /filters/. Defaults to 'custom' if omitted. |
| `geography` | body | `object` | Yes | Geography dict with a required `mode` field. Shapes: • `{"mode": "zips", "zip_codes": ["85001","85004"]}` • `{"mode": "city", "cities": ["Phoenix"], "states": ["AZ"]}` • `{"mode": "counties", "counties": ["Maricopa"], "states": ["AZ"]}` • `{"mode": "states", "states": ["AZ","TX"]}` • `{"mode": "radius", "latitude": 33.45, "longitude": -112.07, "radius": 5}` (miles) |
| `filter_overrides` | body | `object` | No | Filters merged on top of the strategy defaults. See Filter Reference for every accepted key. |
| `requested_count` | body | `integer` | Yes | Maximum rows you'd pay for. 1-25000. `capped_count` in the response = min(total_matches, requested_count). |
| `include_pins` | body | `boolean` | No | When true, the response also includes up to 500 {latitude, longitude} pins for map rendering. Defaults to false for API callers who don't need them. |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/lead-builder/preview/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "strategy": "high_equity_absentee",
    "geography": {"mode": "city", "cities": ["Phoenix"], "states": ["AZ"]},
    "filter_overrides": {"year_built_max": 2015},
    "requested_count": 500
  }'
```

### Example Response (200)

```json
{
  "count": 2340,
  "capped_count": 500,
  "requested_count": 500,
  "max_credit_cost": 2500,
  "max_credit_cost_usd": 50.0,
  "strategy": "high_equity_absentee",
  "strategy_label": "High Equity Absentee",
  "filters_applied": {
    "absentee_owner": true,
    "high_equity": true,
    "state": "AZ",
    "city": "Phoenix"
  }
}
```

---

## POST Execute Lead Build

`POST /v1/api/lead-builder/execute/`

Create a lead list and dispatch the build task. This is the charged path — 5 credits per *delivered* row. Rate limited to 10 executes per hour, 50 per day, per user.

Returns a 202 with the new `id` and a `poll_url`. The build runs asynchronously and typically takes 1-5 minutes depending on `requested_count`. Poll the status endpoint until completion, or configure your account webhook to get notified automatically.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `strategy` | body | `string` | No | Same as /preview/. Defaults to 'custom'. |
| `geography` | body | `object` | Yes | Same shape as /preview/. |
| `filter_overrides` | body | `object` | No | Same shape as /preview/. |
| `requested_count` | body | `integer` | Yes | 1-25000. Billed at 5 credits per delivered row, capped here. |
| `name` | body | `string` | No | Optional label shown in /lead-lists/ and the status response. |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/lead-builder/execute/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "strategy": "high_equity_absentee",
    "geography": {"mode": "city", "cities": ["Phoenix"], "states": ["AZ"]},
    "filter_overrides": {"year_built_max": 2015},
    "requested_count": 500,
    "name": "Phoenix Q2 Prospects"
  }'
```

### Example Response (202)

```json
{
  "id": 42,
  "status": "pending",
  "progress_stage": "",
  "created_at": "2026-04-11T18:23:00Z",
  "requested_count": 500,
  "max_credit_cost": 2500,
  "poll_url": "/v1/api/lead-builder/42/"
}
```

---

## GET Check Build Status

`GET /v1/api/lead-builder/<id>/`

Poll a lead list for completion status. Returns the current stage, progress percent, and the CSV `download_url` once the build finishes.

**Stages:** `fetching_properties` → `skip_tracing` → `generating_csv` → *(empty when complete)*.

Ownership is enforced — you can only poll lead lists your account created. Unknown IDs return 404 (same as cross-user access attempts, to prevent ID enumeration).

**Polling cadence:** poll every 2-5 seconds and back off as the stage advances, or skip polling entirely by configuring your account webhook.

**The CSV:** `download_url` is a time-limited signed URL (valid ~1 hour).

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | `integer` | Yes | The LeadList id returned from the execute endpoint. |

### Example Request

```bash
curl 'https://tracerfy.com/v1/api/lead-builder/42/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
// Complete — download_url ready
{
  "id": 42,
  "name": "Phoenix Q2 Prospects",
  "strategy": "high_equity_absentee",
  "strategy_label": "High Equity Absentee",
  "source": "api",
  "status": "complete",
  "progress_stage": "",
  "progress_percent": 100,
  "requested_count": 500,
  "actual_count": 487,
  "credits_deducted": 2435,
  "download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/lead_list_42_a1b2c3d4.csv",
  "error_message": "",
  "created_at": "2026-04-11T18:23:00Z",
  "completed_at": "2026-04-11T18:37:42Z",
  "poll_url": "/v1/api/lead-builder/42/"
}

// Pending — still building
{
  "id": 42,
  "name": "Phoenix Q2 Prospects",
  "strategy": "high_equity_absentee",
  "strategy_label": "High Equity Absentee",
  "source": "api",
  "status": "pending",
  "progress_stage": "skip_tracing",
  "progress_percent": 47,
  "requested_count": 500,
  "actual_count": null,
  "credits_deducted": 0,
  "download_url": "",
  "error_message": "",
  "created_at": "2026-04-11T18:23:00Z",
  "completed_at": null,
  "poll_url": "/v1/api/lead-builder/42/"
}
```

---

## GET Lead List Rows (JSON)

`GET /v1/api/lead-builder/<id>/rows/`

Paginated JSON access to the rows in a completed lead list. Same data as the CSV download, delivered as a JSON array — no file parsing needed.

**Use this when:** you want to consume lead data programmatically without downloading and parsing a CSV. Perfect for CRM integrations, webhooks, or piping rows directly into your application.

**No extra charge:** the data was already paid for on /execute/. This endpoint is a free read.

**Pagination:** defaults to 100 rows per page (max 500). Use `page` and `per_page` query params. Returns 409 if the list is still processing.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | `integer` | Yes | The LeadList id from /execute/. |
| `page` | query | `integer` | No | Page number (default 1). |
| `per_page` | query | `integer` | No | Rows per page, 1-500 (default 100). |

### Example Request

```bash
curl 'https://tracerfy.com/v1/api/lead-builder/42/rows/?page=1&per_page=100' \
  -H 'Authorization: Bearer <YOUR_TOKEN>'
```

### Example Response (200)

```json
{
  "lead_list_id": 42,
  "total_rows": 500,
  "page": 1,
  "per_page": 100,
  "total_pages": 5,
  "rows": [
    {
      "address": "742 Evergreen Terrace",
      "city": "Phoenix",
      "state": "AZ",
      "zip_code": "85032",
      "county": "Maricopa",
      "property_type": "SFR",
      "year_built": 1985,
      "beds": 4,
      "baths": 2.5,
      "building_size_sqft": 2140,
      "lot_size_sqft": 8712,
      "estimated_value": 485000,
      "estimated_equity": 298000,
      "equity_percent": 61.4,
      "assessed_value": 362000,
      "open_mortgage_balance": 134500,
      "lender_name": "Wells Fargo",
      "estimated_mortgage_payment": 987,
      "total_properties_owned": 2,
      "total_portfolio_value": 910000,
      "roof_material": "Composition Shingle",
      "roof_construction": "Gable",
      "flood_zone": false,
      "cash_buyer": false,
      "corporate_owned": false,
      "owner_1_first_name": "JANE",
      "owner_1_last_name": "DOE",
      "owner_2_first_name": "",
      "owner_2_last_name": "",
      "mail_address": "742 Evergreen Terrace",
      "mail_city": "Phoenix",
      "mail_state": "AZ",
      "mail_zip": "85032",
      "primary_phone": "4805551234",
      "primary_phone_type": "Mobile",
      "primary_phone_carrier": "T-Mobile",
      "primary_phone_dnc": false,
      "primary_phone_tcpa": false,
      "mobile_1": "4805559876",
      "mobile_1_dnc": true,
      "mobile_1_tcpa": false,
      "email_1": "jane.doe@example.com",
      "email_2": "",
      "email_3": "",
      "has_contact": true,
      "contact_clean": true,
      "absentee_owner": false,
      "owner_occupied": true,
      "high_equity": true,
      "vacant": false,
      "pre_foreclosure": false,
      "free_clear": false,
      "last_sale_date": "2009-04-15",
      "last_sale_price": 187000,
      "prior_sale_date": "2001-08-22",
      "prior_sale_price": 142000,
      "document_type": "Warranty Deed",
      "recording_date": "2009-04-17"
    },
    {
      "address": "1200 Oak Blvd",
      "city": "Phoenix",
      "state": "AZ",
      "zip_code": "85018",
      "county": "Maricopa",
      "property_type": "SFR",
      "year_built": 1972,
      "beds": 3,
      "baths": 2.0,
      "building_size_sqft": 1680,
      "lot_size_sqft": 6200,
      "estimated_value": 395000,
      "estimated_equity": 395000,
      "equity_percent": 100.0,
      "assessed_value": 288000,
      "open_mortgage_balance": 0,
      "lender_name": "",
      "estimated_mortgage_payment": 0,
      "total_properties_owned": 1,
      "total_portfolio_value": 395000,
      "roof_material": "Tile/Clay",
      "roof_construction": "Hip",
      "flood_zone": false,
      "cash_buyer": false,
      "corporate_owned": false,
      "owner_1_first_name": "ROBERT",
      "owner_1_last_name": "SMITH",
      "owner_2_first_name": "LINDA",
      "owner_2_last_name": "SMITH",
      "mail_address": "PO Box 4412",
      "mail_city": "Scottsdale",
      "mail_state": "AZ",
      "mail_zip": "85261",
      "primary_phone": "6025559900",
      "primary_phone_type": "Landline",
      "primary_phone_carrier": "CenturyLink",
      "primary_phone_dnc": false,
      "primary_phone_tcpa": false,
      "mobile_1": "",
      "mobile_1_dnc": false,
      "mobile_1_tcpa": false,
      "email_1": "rsmith72@example.com",
      "email_2": "",
      "email_3": "",
      "has_contact": true,
      "contact_clean": true,
      "absentee_owner": true,
      "owner_occupied": false,
      "high_equity": true,
      "vacant": false,
      "pre_foreclosure": false,
      "free_clear": true,
      "last_sale_date": "1998-11-03",
      "last_sale_price": 95000,
      "prior_sale_date": null,
      "prior_sale_price": null,
      "document_type": "Warranty Deed",
      "recording_date": "1998-11-05"
    },
    "... 98 more rows in this page"
  ]
}
```

---

## POST Single-Address Lookup

`POST /v1/api/lead-builder/lookup/`

Synchronous single-address version of /execute/ — one address in, one full lead row out. JSON response, no CSV. Use this when you already have a specific address and want the complete data (property attributes, owner, full skip trace) in a single API call.

**Billing — 10 credits per property hit:**
• Property found + skip trace hit → 10 credits, full phones/emails + all compliance flags
• Property found + skip trace miss → 10 credits, full property data, empty `contacts` block
• Property NOT found → 0 credits, `hit: false`

Priced at 2× the /v1/api/trace/lookup/ endpoint (5 credits) because this lookup returns the full property dossier (50+ CSV columns) *in addition to* skip-trace contacts. If you only need phones and emails for an address (no property attributes), use instant trace instead — same skip-trace data, half the cost.

**The response shape:** the `property`, `owner`, and `contacts` blocks together contain every column from the CSV schema — flatten them to get a single row ready for pandas / spreadsheet ingestion.

**ZIP code:** optional but *strongly recommended* — without it, common street names can match the wrong property in a large city.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `address` | body | `string` | Yes | Property street address. Example: "4521 E Monte Cristo Ave". |
| `city` | body | `string` | Yes | Property city. |
| `state` | body | `string` | Yes | Property state (2-letter code). |
| `zip_code` | body | `string` | No | Property ZIP. Optional but strongly recommended for accuracy. |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/lead-builder/lookup/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "address": "4521 E Monte Cristo Ave",
    "city": "Phoenix",
    "state": "AZ",
    "zip_code": "85032"
  }'
```

### Example Response (200)

```json
// Hit — 10 credits deducted
{
  "hit": true,
  "credits_deducted": 10,
  "skip_trace_hit": true,
  "property": {
    "address": "4521 E Monte Cristo Ave",
    "city": "Phoenix",
    "state": "AZ",
    "zip_code": "85032",
    "county": "Maricopa",
    "latitude": 33.6,
    "longitude": -112.0,
    "apn": "123-45-6789",
    "subdivision": "Paradise Park",
    "property_type": "SFR",
    "property_use": "Single Family",
    "land_use": "Residential",
    "year_built": 1978,
    "beds": 4,
    "baths": 2.5,
    "units_count": 1,
    "stories": 1,
    "building_size_sqft": 2140,
    "lot_size_sqft": 8712,
    "has_ac": true,
    "has_garage": true,
    "has_pool": false,
    "has_basement": false,
    "has_deck": true,
    "estimated_value": 485000,
    "estimated_equity": 298000,
    "equity_percent": 61.4,
    "assessed_value": 362000,
    "last_sale_date": "2009-04-15",
    "last_sale_price": 187000,
    "years_owned": 17,
    "prior_sale_date": "2001-08-22",
    "prior_sale_price": 142000,
    "open_mortgage_balance": 134500,
    "lender_name": "Wells Fargo",
    "estimated_mortgage_payment": 987,
    "total_properties_owned": 2,
    "total_portfolio_value": 910000,
    "cash_buyer": false,
    "corporate_owned": false,
    "roof_material": "Composition Shingle",
    "roof_construction": "Gable",
    "flood_zone": false,
    "document_type": "Warranty Deed",
    "recording_date": "2009-04-17",
    "absentee_owner": false,
    "owner_occupied": true,
    "vacant": false,
    "free_clear": false,
    "high_equity": true,
    "pre_foreclosure": false,
    "tax_delinquent": false
  },
  "owners": [
    {
      "first_name": "JANET",
      "last_name": "MORRIS"
    },
    {
      "first_name": "ROBERT",
      "last_name": "MORRIS"
    }
  ],
  "mailing_address": {
    "address": "4521 E Monte Cristo Ave",
    "city": "Phoenix",
    "state": "AZ",
    "zip": "85032"
  },
  "contacts": {
    "phones": [
      {
        "number": "4805551234",
        "type": "Mobile",
        "dnc": false,
        "tcpa": false,
        "carrier": "T-Mobile",
        "rank": 1
      },
      {
        "number": "4805559876",
        "type": "Mobile",
        "dnc": true,
        "tcpa": false,
        "carrier": "AT&T",
        "rank": 2
      },
      {
        "number": "4805550100",
        "type": "Landline",
        "dnc": false,
        "tcpa": false,
        "carrier": "CenturyLink",
        "rank": 3
      }
    ],
    "emails": [
      {
        "email": "janet.morris@example.com",
        "rank": 1
      }
    ],
    "litigator": false,
    "has_contact": true,
    "contact_clean": false
  }
}

// Miss — address not found, 0 credits
{
  "hit": false,
  "credits_deducted": 0,
  "address": "999 Nowhere Blvd",
  "city": "Austin",
  "state": "TX",
  "zip_code": "78701"
}
```

---

## POST Lead Builder Webhooks

`POST Account.webhook_url`

When a Lead Builder list completes, Tracerfy POSTs the result to the `webhook_url` configured on your account — the same account-level URL used for skip trace and DNC webhooks. This eliminates the need to poll /status/ — your server gets notified the moment the CSV is ready.

**How to use:** set your `webhook_url` in your account settings. When any lead list finishes (success or failure), Tracerfy sends a POST with the payload below. Your endpoint should return 2xx within 10 seconds.

**Retry:** Tracerfy does not retry failed webhook deliveries. If your server is down when the webhook fires, use /status/ as a fallback to check completion.

### Headers

| Name | Value |
|------|-------|
| `Content-Type` | `application/json` |

### Example Request

```bash
Tracerfy sends this JSON to your Account.webhook_url when a lead list completes.
```

### Example Response (200)

```json
{
  "id": 42,
  "type": "lead_list",
  "name": "Phoenix Q2 Prospects",
  "strategy": "high_equity_absentee",
  "source": "api",
  "created_at": "2026-04-11T18:23:00Z",
  "completed_at": "2026-04-11T18:37:42Z",
  "pending": false,
  "download_url": "https://tracerfy.nyc3.cdn.digitaloceanspaces.com/tracerfy/lead_list_a1b2c3d4.csv",
  "requested_count": 500,
  "actual_count": 487,
  "credits_deducted": 2435
}
```

---

## POST AI Assist (TraceAI)

`POST /v1/api/lead-builder/ai-assist/`

Translate a plain-English description of your ideal customer into a structured `strategy` + `geography` + `filter_overrides` spec you can pipe straight into `/preview/` or `/execute/`. This is the fastest way to build a lead list from code — one call to describe, one call to run.

**Billing:** 1 credit per successful response. Failed calls (503) are not charged.

**When the AI isn't sure:** if your prompt is ambiguous ('Springfield', 'LA'), the response will include `needs_clarification: true`, a `clarifying_question`, and a `conversation_id`. To reply, pass the same `conversation_id` along with the user's answer in a new call — the AI loads the full conversation history and picks up where it left off.

**Conceptual questions** like "what is a tired landlord" return `needs_clarification: true` with the explanation in `rationale.summary` — don't run a search on those, ask the user where they want to target first.

**Neighborhood-level targeting:** say "plumbing leads in South Side Chicago" or "downtown Nashville probate" and TraceAI will pick the ZIP codes that cover the area from its general knowledge. No need to hand-roll ZIP lists.

### Headers

| Name | Value |
|------|-------|
| `Authorization` | `Bearer <YOUR_TOKEN>` |
| `Content-Type` | `application/json` |

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `prompt` | body | `string` | Yes | Plain-English description of the target audience. 5-2000 characters. Example: 'plumbing leads in South Side Chicago' or 'absentee owners with 50%+ equity in Tampa built before 1990'. |
| `conversation_id` | body | `uuid` | No | UUID from a previous response. Pass this to continue a clarification thread — the AI loads all prior turns automatically. |

### Example Request

```bash
curl -X POST 'https://tracerfy.com/v1/api/lead-builder/ai-assist/' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"prompt": "plumbing leads in South Side Chicago"}'
```

### Example Response (200)

```json
// Successful — filters applied, ready to pipe into /preview/ or /execute/
{
  "strategy": "custom",
  "strategy_label": "Custom",
  "geography": {
    "mode": "zips",
    "zip_codes": [
      "60609",
      "60615",
      "60617",
      "60619",
      "60620",
      "60621"
    ]
  },
  "filter_overrides": {
    "property_type": "SFR",
    "year_built_max": 2000,
    "absentee_owner": false
  },
  "rationale": {
    "summary": "Owner-occupied single-family homes in the South Side Chicago ZIP codes built before 2000 — older plumbing systems are common in this age range.",
    "steps": [],
    "assumptions": [],
    "warnings": [
      "Applied Year Built ≤ 2000. To include rentals, remove the absentee-owner filter."
    ]
  },
  "confidence": 0.88,
  "needs_clarification": false,
  "clarifying_question": null,
  "conversation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "credits_deducted": 1
}

// Clarification needed — AI needs more info before it can search
{
  "strategy": "custom",
  "strategy_label": "Custom",
  "geography": {
    "mode": "city",
    "cities": [
      "(pending)"
    ]
  },
  "filter_overrides": {},
  "rationale": {
    "summary": "I can help find leads in Austin, but I need to know what kind. Investors, distressed sellers, homeowners for a service, or something else?",
    "steps": [],
    "assumptions": [],
    "warnings": []
  },
  "confidence": 0.3,
  "needs_clarification": true,
  "clarifying_question": "What kind of leads are you looking for? Are you targeting investors, distressed sellers, or homeowners for a specific service?",
  "conversation_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
  "credits_deducted": 1
}
```

---

## REF Filter Reference

`REF Accepted keys for filter_overrides`

Every key accepted in `filter_overrides`, grouped for readability. Unknown keys are rejected with a 400.GroupKeysProperty`property_type`, `property_types`, `beds_min`, `beds_max`, `baths_min`, `baths_max`, `units_min`, `units_max`, `building_size_min`, `building_size_max`, `lot_size_min`, `lot_size_max`, `year_built_min`, `year_built_max`, `stories_min`, `stories_max`, `rooms_min`, `rooms_max`, `pool`, `garage`, `basement`, `deck`, `mfh_2to4`, `mfh_5plus`Value &amp; Equity`value_min`, `value_max`, `assessed_value_min`, `assessed_value_max`, `estimated_equity`, `estimated_equity_min`, `estimated_equity_max`, `equity`, `equity_operator`, `high_equity`, `free_clear`, `ltv_min`, `ltv_max`Ownership`absentee_owner`, `in_state_owner`, `out_of_state_owner`, `individual_owned`, `trust_owned`, `corporate_owned`, `cash_buyer`, `investor_buyer`, `private_lender`, `properties_owned_min`, `properties_owned_max`, `years_owned_min`, `years_owned_max`Portfolio signals`portfolio_value_min/max`, `portfolio_equity_min/max`, `portfolio_mortgage_balance_min/max`, `portfolio_purchased_last6_min/max`, `portfolio_purchased_last12_min/max`Distress`vacant`, `pre_foreclosure`, `pre_foreclosure_date_min`, `pre_foreclosure_date_max`, `auction`, `reo`, `notice_type`, `search_range`MLS`mls_active`, `mls_pending`, `mls_cancelled`, `mls_days_on_market_min`, `mls_days_on_market_max`, `mls_listing_price_min`Mortgage`mortgage_min`, `mortgage_max`, `adjustable_rate`, `assumable`, `loan_type_code_first`, `open_mortgages_min`, `open_mortgages_max`Sale history`last_sale_date_min`, `last_sale_date_max`, `last_sale_price_min`, `last_sale_price_max`, `last_sale_arms_length`Mailing address`mail_city`, `mail_state`, `mail_zip`, `mail_county`Environment`flood_zone`, `flood_zone_type`

---
