# Site API reference

> The endpoints a dealer's own website calls: shop, vehicles, one vehicle, and leads. Authentication, limits and errors.

Source: https://www.wheewise.com/docs/site-api-reference

Base address: `https://dealer.wheewise.com/api/site/v1`. A machine-readable description is always current at [openapi.json](https://dealer.wheewise.com/api/site/v1/openapi.json).

## Authentication

- Send the site key in the `X-Wheewise-Site-Key` header (or as `?key=` on a `GET`).
- From a browser, the request's origin must be on the allowed list; otherwise the answer is `403` and the browser blocks it. A server call without an `Origin` header works with the key alone.
- Preflight (`OPTIONS`) requests are answered for any origin. Only `GET`, `POST` and `OPTIONS` are allowed; credentials and cookies are never used.

## GET /shop

The shop's public details: `name`, `city`, `address`, `bio`, `business_hours`, `map_link`, `logo_url`, `cover_photo_url`, `hero_image_url`, `gallery_urls`, `live_vehicles`, `wheewise_page`.

## GET /vehicles

The dealer's **published** vehicles only (drafts, paused and sold ones never appear).

| Query | Meaning |
| --- | --- |
| `limit` (1 to 50, default 20), `offset` | Paging. |
| `q` | Searches make, model and variant. |
| `make`, `category`, `fuel` | Filters. `fuel` is PETROL, DIESEL, CNG, ELECTRIC or HYBRID. An unknown category or fuel returns 400. |
| `min_price`, `max_price`, `min_year` | Ranges. |
| `sort` | `newest` (default), `price_asc`, `price_desc`, `year_desc`. |

Returns `{ "total": 31, "vehicles": [ … ] }`. Each vehicle has `id`, `slug`, `url`, `make`, `model`, `variant`, `year`, `fuel_type`, `transmission`, `odometer_km`, `asking_price` (rupees), `city`, `description`, `photo_urls` (the first is the cover), `finance_available`, `color`, `body_type`, `seating_capacity`, `cubic_capacity_cc`, `horsepower_bhp`, `insurance_valid_months`, `puc_valid_months`, `created_at`, `updated_at`. Costs, margins, registration and chassis numbers are never included.

## GET /vehicles/{id}

One published vehicle, by `id` or `slug`. `404` if it is not published.

## POST /leads

Sends an enquiry or a test-drive request to the dealer. It appears under Leads in the dealer app and the dealer is notified.

```json
{
  "name": "Ravi Kumar",
  "phone": "+91 98765 43210",
  "email": "ravi@example.com",
  "message": "Is it still available?",
  "vehicle_id": "<an id from /vehicles>",
  "kind": "TEST_DRIVE",
  "preferred_at": "2026-11-01T10:30:00+05:30",
  "website": ""
}
```

- `name`, `phone` and `email` are required. `kind` is `ENQUIRY` (default) or `TEST_DRIVE`.
- `TEST_DRIVE` needs a `vehicle_id` of a published vehicle and a `preferred_at` that is in the future and within 90 days (ISO 8601 with an offset).
- `website` is a **spam trap**: add a hidden form field with this name and leave it empty.
- The same person asking about the same vehicle (same kind) twice in a day is stored once.

## Limits and errors

| Status | Meaning |
| --- | --- |
| 400 | A field is missing or invalid; `error` says which. |
| 401 | No site key, or the key was replaced. |
| 403 | The website is not on the allowed list. |
| 404 | Vehicle or shop not available. |
| 413 | Request body over 4 KB. |
| 429 | Too many requests (per visitor, per key, or per shop per day). |

Errors look like `{ "error": "message" }` and are written to be shown to a visitor. `GET` responses may be cached by the browser for 30 seconds.

## A minimal client

```js
const API = "https://dealer.wheewise.com/api/site/v1";
const SITE_KEY = "wws_...";

export async function wheewise(path, init = {}) {
  const res = await fetch(API + path, {
    ...init,
    headers: { "X-Wheewise-Site-Key": SITE_KEY, "Content-Type": "application/json", ...init.headers },
  });
  const body = await res.json();
  if (!res.ok) throw new Error(body.error || "Request failed");
  return body;
}

export const getVehicles = (params = {}) => wheewise("/vehicles?" + new URLSearchParams(params));
export const sendLead = (lead) => wheewise("/leads", { method: "POST", body: JSON.stringify(lead) });
```
