---
title: "Dealer leads"
description: "Entity-visible dealer leads — test drives, chats, trades, service, and partner applications — each with a rooftop messenger thread"
locale: "en"
---
# Dealer leads

On this pack an opportunity is a lead to a rooftop or to the dealer group. The shopper and that team's members share one messenger thread. The public browse feed is not how a store works a test drive.

> **Info**
> This page is the dealer-pack overlay of the opportunities article. Matcher, auto-fill, and the generic offer/request types still exist on Layer 1. The types below are the ones this pack adds and the clone enables.

  Creating a lead stores `metadata.conversationId` and the shopper is sent to `/messages?c=`. Chat about the same vehicle reuses the open inquiry. The product agent is not that thread.

## Lead kinds

| Kind | Who starts it | Desk meaning |
|---|---|---|
| `vehicle_inquiry` | Chat with seller, or check availability | One open thread per shopper and vehicle |
| `test_drive` | Card dialog or vehicle page | Date and time of day |
| `trade_in` | Vehicle page or sell/trade | VIN and miles, optional asking price |
| `finance_inquiry` | Ask about financing | A question. No SSN, no credit pull |
| `service_appointment` | `/service` | Rooftop, date, and a note |
| `product_offer` | Seller offer, other products | Title, category, optional SKU and price |
| `partner_application` | How I shop, dealer or supplier | One open application per person. Not a staff role |

Enabled on the DealerDirect clone in `ring-config.json` → `opportunities.enabledTypes`, together with `offer`, `request`, and `job`.

Stages on the desk: `new`, `contacted`, `appointment_set`, `showed`, `sold`, `lost`.

### For founders

## How a day on the desk works

A shopper taps Chat, Test drive, Value my trade, Ask about financing, Book service, or sends an offer. They must be signed in. They land in Messages with your team already on the thread. You work the row at `/dealer/leads`.

Sold and lost inquiries do not reopen. A new chat after that starts a new lead. A second chat while the inquiry is still open returns the same thread.

  
- **[Vehicle](/docs/features/store.md)** — The buttons live on the inventory card and the vehicle page.

  
- **[Who is notified](/docs/features/entities.md)** — Members of that rooftop, and members of the parent group, can see the lead.

Do not treat Ask about financing as a pre-approval. The copy tells the shopper there is no credit pull and no SSN. Do not add those fields.

### For developers

## Create path

`POST /api/dealer/leads` reads the session and calls `createDealerLead` in `features/dealer/services/leads.ts`.

The opportunity is `visibility: 'entity'`, `organizationId` is the rooftop or the group, and `metadata` holds `leadKind`, `productId`, `vin`, `stockNumber`, `stage: 'new'`, and the form extras. `openEntityLeadConversation` uses conversation type `opportunity`, participant ids = shopper + `getEntityTeamUserIds`, and notifies the team with `actionUrl: '/dealer/leads'`.

`rememberConversation` merges `conversationId` into existing metadata. The database update is a shallow JSON merge, so the helper reads the current metadata and spreads it. Passing only `{ conversationId }` would wipe `stage` and the VIN.

Reuse queries:

- `vehicle_inquiry` where `createdBy`, `metadata.productId` match and stage is not `sold` or `lost`.
- `partner_application` where `createdBy` matches and stage is not `sold` or `lost`.

`GET /api/dealer/leads` returns rows the caller can see via `loadViewerEntityAccessIds`. Stage updates go through `features/dealer/services/lead-pipeline.ts` and require entity role `MEMBER` or a platform admin.

Policy for every kind above lives in `features/opportunities/lib/opportunity-type-policy.ts` (`minRole: subscriber`, `defaultVisibility: entity`). `partner_application` is not on the four-tile add-opportunity picker (`OPPORTUNITY_SELECTOR_TYPE_ORDER`). It is created only from the audience API.

{`{
  "kind": "vehicle_inquiry",
  "productId": "",
  "rooftopId": "",
  "note": "Shopper asked to chat about 2020 Honda Civic",
  "source": "inventory"
}`}

A 200 body is `{ opportunityId, conversationId, rooftopId }`. The client helper `post-dealer-lead.ts` routes to `/messages?c=`.

## Frequently asked questions

### Impact

#### Is this the product agent?

No. The agent uses `agentChat=1` and conversation type `product`. Dealer leads use type `opportunity` and the rooftop team.

#### Can a guest send a lead?

No. The API returns 401. The button sends them to login and back to the vehicle, service page, or sell page.

### Migration

#### What happened to the generic opportunity article's matcher loop?

It still runs inside Layer 1 `createOpportunity` for types that go through that service. This page documents the dealer kinds and the thread handoff. Do not quote matcher accuracy percentages.

### Ops

#### Why would chat open a thread with only the shopper?

The rooftop entity had no members in `memberRoles`. Add staff on the entity. The lead row still exists.

## Not in this release

  
- [features/store](/docs/features/store.md) — Prerequisite: the vehicle the lead is about.

  
- [features/entities](/docs/features/entities.md) — Depends on: the rooftop or group the lead is addressed to.
