---
title: "Vehicle stock"
description: "How a rooftop's vehicles get onto the store, how a sold unit leaves the advertisement, and which feed add-ons are not connected"
locale: "en"
---
# Vehicle stock

A vehicle on the website is a store product with a VIN, an asking price, and a rooftop. If it has no VIN, or no price, it does not publish. Shoppers never add it to a cart.

> **Info**
> This is the dealer-pack page for the `features/inventory` sidebar slot. It is stock for a rooftop, not a warehouse ERP screen.

## Status you can put on a unit

| Status | Meaning on the site |
|---|---|
| `available` | Can be shown |
| `pending` | Still a dealer vehicle; your desk copy should say it is spoken for |
| `in_transit` | Not on the lot yet |
| `sold` | Removed from the public advertisement after delivery, or after a full import that no longer lists that VIN |

### For founders

## Putting cars on the site

Staff who are admins of a rooftop open `/dealer/inventory/import`. Paste a JSON array of vehicles, type that rooftop's entity id, and use **Dry run** first. Dry run counts what would be created or updated and does not write. **Apply** writes. The screen always sends an incremental import: it adds and updates the VINs in the file and leaves other units alone.

Each row needs a 17-character VIN and an asking price. Rows that are only a title are filled from NHTSA vPIC when the title does not contradict the VIN. A row that disagrees with the VIN decode is skipped, and the report says why.

The same screen lists three names as planned and not connected: Carfax history reports, vAuto pricing, and CDK DMS sync. Do not promise those to a customer.

### When the car is sold

Delivery is a staff action, not a button on the public page. A rooftop member calls `POST /api/dealer/vehicles//deliver` with the buyer's user id. That sets the unit to sold, zeroes the public stock, and can place the car in the buyer's garage. If you also pass an opportunity id, that lead moves to `sold`.

A full import (`mode: full` on the API, not on the current screen) marks VINs that are no longer in the file as sold. Use that only when the file is the whole rooftop, not a partial list.

### For developers

| Piece | Path |
|---|---|
| Import screen | `app/[locale]/(protected)/dealer/inventory/import/page.tsx` |
| API | `POST /api/dealer/inventory/import` |
| Rules | `features/dealer/services/inventory-import.ts` |
| Feed shape | `features/dealer/lib/feed-map.ts` |
| VIN decode | `features/dealer/services/vpic.ts` |
| Deliver | `POST /api/dealer/vehicles/[id]/deliver` |

The API requires a session. A non-admin must pass `assertEntityRole` at `ADMIN` for that rooftop. Platform admins skip that check. Omitted `dryRun` is treated as true. Omitted `mode` is `incremental`. Product id is stable per VIN and rooftop (`productIdFor`).

`features/dealer/addons.ts` is the list of unpaid feeds. Do not add client keys for them in the pack.

## Frequently asked questions

### Impact

#### Will a dry run change the website?

No. Apply is the button that writes.

#### Can a salesperson import?

Not unless they are an admin of that rooftop, or a platform admin. A member can deliver a sold unit. Import is the stricter role.

### Ops

#### Why was a row skipped?

The report lists the VIN. Usual causes: the VIN is not 17 characters, there is no asking price, or the title does not match the decode.

  
- [features/store](/docs/features/store.md) — Next-step: how a published unit looks to a shopper.

  
- [features/vendor-management](/docs/features/vendor-management.md) — Same workflow: typing a vehicle in by hand.

  
- [getting-started/first-success](/docs/getting-started/first-success.md) — Prerequisite: the screens an owner opens first.
