> ## Documentation Index
> Fetch the complete documentation index at: https://api.visor.vin/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Make your first authenticated requests to the Visor Public API.

The public beta API is available at:

```txt theme={null}
https://api.visor.vin/v1
```

The generated OpenAPI document is public:

```txt theme={null}
https://api.visor.vin/v1/openapi.json
```

All other `/v1` endpoints require an API key:

```sh theme={null}
export VISOR_API_KEY="vis_live_..."

curl "https://api.visor.vin/v1/listings?limit=2&make=toyota&state=CA" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Do not send API keys in query strings. Requests such as `?api_key=...` are rejected.

## MCP server for agents

Prefer to let an AI agent query Visor for you? The hosted MCP server lets Claude, ChatGPT, Codex, and other agents call the Public API in plain language — no HTTP requests to write:

```txt theme={null}
https://mcp.visor.vin/mcp
```

Most apps connect in under a minute: you sign in to Visor and pick your account, with no key to copy. Clients that set request headers can authenticate with a dashboard token sent as `Authorization: Bearer <VISOR_API_KEY>` instead.

See [Connect to your AI agent](/docs/mcp-setup) for step-by-step setup for Claude, ChatGPT, Claude Code, Codex, OpenCode, and the OpenAI and Claude APIs, plus how the `search` and `execute` tools work.

## Listings

```sh theme={null}
curl "https://api.visor.vin/v1/listings?limit=10&offset=0&make=toyota&model=camry&year=2024&state=CA&sort=price" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Use `/v1/listings` with `limit` and `offset` for pagination. The deprecated `/v1/listings/search` compatibility path still accepts `per_page` and one-based `page`, maps them to `limit` and `offset`, and returns deprecation headers. Migrate old requests such as `/v1/listings/search?per_page=100&page=2` to `/v1/listings?limit=100&offset=100`.

Useful filters include vehicle fields (`make`, `model`, `trim`, `year`, `body_type`, `transmission`, `drivetrain`, `fuel_type`, `powertrain_type`, `engine`, colors, `seating_capacity`, `cylinders`, `doors`), dealer/inventory fields (`dealer_id`, `state`, `dealer_type`, `availability_status`, `inventory_type`), option and keyword fields (`options_packages`, `features`, `keywords` plus `exclude_*` variants), VIN masks (`vin_pattern`), ranges (`min_price`, `max_price`, `min_mileage`, `max_mileage`, `min_msrp`, `max_msrp`, `min_days_on_market`, `max_days_on_market`), and geography (`postal_code` or `latitude`/`longitude` with `radius`, plus `bbox` for map viewports).

Closed-vocabulary filters reject unsupported values instead of returning a silent empty result. `inventory_type` accepts `new`, `used`, and `certified`; `cpo` is accepted as an alias for `certified`. `keywords` is for provenance/history tokens only: `one_owner`, `clean_title`, `branded`, and `fleet`. Discover open categorical values, such as `trim`, `version`, `assembly_country`, `features`, and `options_packages`, with `/v1/facets` before filtering.

Use `fields` to return specific public listing summary fields. The default response fields do not change unless `fields` is supplied. `id` and `vin` are always returned, and `default` includes the normal listing summary fieldset plus selected extras. Unknown fields are rejected:

```sh theme={null}
curl "https://api.visor.vin/v1/listings?dealer_id=dealer-1,dealer-2&fields=default,exterior_color,interior_color,msrp&limit=100" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Supported `fields` values are:

```txt theme={null}
default, id, vin, year, make, model, trim, version,
body_type, drivetrain, fuel_type, powertrain_type, transmission, engine,
cylinders, doors, seating_capacity,
exterior_color, interior_color, base_exterior_color, base_interior_color,
msrp, price, miles, days_on_market, listed_at, status, inventory_status, availability_status,
inventory_type, stock_number, vdp_url, sold_date, dealer_id, dealer_name, dealer_type, city, state,
postal_code, latitude, longitude, distance_miles, photo_urls, features, options_packages,
discount_from_msrp
```

Use `vin_pattern` to filter by one or more positions in a 17-character US VIN. Pass up to 10 comma-separated masks. VIN characters match themselves, `?` matches exactly one VIN position, and `*` may appear only at the end. Short masks are treated as prefixes, so `vin_pattern=1HG` is equivalent to `1HG*`. Examples: `vin_pattern=1HG` for WMI, `vin_pattern=1HGCM826*` for WMI plus VDS, and `vin_pattern=???????J*` for a specific eighth VIN character. VIN input is normalized to uppercase and may not contain `I`, `O`, or `Q`.

Sort listing collections with field names. Prefix a field with `-` for descending order. The default is `sort=days_on_market`, which returns newest listings first. Supported fields are `days_on_market`, `listed_at`, `price`, `miles`, `msrp`, `discount`, and `distance`. `discount_from_msrp` is a signed fraction calculated as `(price - combined_msrp) / combined_msrp`; it uses combined MSRP, not base MSRP. Negative values are below MSRP. `sort=discount` returns the best discount from MSRP first. `sort=distance` requires `postal_code` or `latitude`/`longitude`.

Use `listed_after=<ISO timestamp>` for incremental sync workflows. For example, `/v1/listings?listed_after=2026-06-28T15:30:00Z&sort=listed_at&fields=default,listed_at,postal_code` returns listings first observed by Visor on or after that timestamp. Treat `listed_at` as Visor's first-observed listing timestamp and de-duplicate by `id` or `vin` when polling across overlapping windows.

`inventory_status=active` is the default. Use `inventory_status=sold` or `sold_within_days=30` for sold inventory. A listing is sold only after it has been missing from the dealer's inventory feed for at least three days and passed sold-quality checks; before then it is missing, not sold. Use `snapshot_date=YYYY-MM-DD` for historical active-inventory snapshots. `snapshot_date` cannot be combined with sold inventory filters.

Public inventory is deduplicated by VIN and assigned to the dealer Visor believes owns the listing. Dealer-scoped inventory counts can differ from raw dealer feed counts when the same VIN appears in multiple dealer feeds.

`price` is the dealer's main advertised listing price when Visor has a usable value. It may or may not include fees, add-ons, incentives, taxes, registration, or other adjustments depending on how the dealer presents pricing.

VIN and listing detail responses can also include nullable `pricing` detail when supported provider data is available. `pricing` does not replace `price`; it gives named pricing calculations and provider-observed line items that explain the listing's displayed pricing context:

| Field                                             | Meaning                                                                                                                                                             |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `displayed_provider_current_price_usd`            | Provider-displayed listing price for broad shopper consumption at observation time. It excludes MSRP fallback and does not imply the lowest eligible price.         |
| `seller_total_before_taxes_and_registration_usd`  | Comparable broad-buyer seller-side total before government taxes, title, registration, license, lien, and similar statutory charges.                                |
| `seller_total_before_manufacturer_incentives_usd` | Dealer-controlled seller total before manufacturer-funded incentives and rebates, including broadly available temporary programs.                                   |
| `dealer_adjusted_vehicle_price_usd`               | Vehicle price after dealer-controlled vehicle adjustments, before seller fees, seller add-ons, government charges, and manufacturer incentives.                     |
| `line_items`                                      | Provider-observed pricing row evidence. Each item includes amount, raw label, normalized role, applicability, direction, row key/order, and subtype when available. |

Fetch one listing:

```sh theme={null}
curl "https://api.visor.vin/v1/listings/listing-1?include=price_history,options" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Listing detail is listing-centered. Vehicle facts are nested under `vehicle`, with installed options under `vehicle.build.options` when requested. When supported provider data is available, listing pricing detail is returned at `data.pricing`; otherwise it is `null`.

Include price history or decoded installed options directly on listing search pages:

```sh theme={null}
curl "https://api.visor.vin/v1/listings?inventory_status=sold&postal_code=98101&radius=100&limit=100&fields=default,msrp,discount_from_msrp,features,options_packages,sold_date&include=price_history,options" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

`include=price_history` adds a `price_history` array to each listing in the returned page. `include=options` adds an `options` array of `{ code, name, msrp }` objects decoded from the listing's option/package codes. Listing collection pages remain capped at 100 records and are priced as enriched listing search requests when either expansion is requested.

## Facets

```sh theme={null}
curl "https://api.visor.vin/v1/facets?facets=make,model,price&make=toyota&state=CA" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Facets use the listing filter surface except app/control fields such as `limit`, `offset`, `sort`, `fields`, `radius_id`, `columns`, `option_sort`, and `agnostic`. The `facets` parameter is required; pass a comma-separated selection such as `facets=make,model,price`.

Categorical facets return up to 20 values by default. Use `facet_value_limit` to request a different per-facet value limit, up to 100. Values above 100 are rejected with `validation_error`; they are not silently clamped. Numeric range facets use fixed buckets and are not affected by `facet_value_limit`.

Supported facet names are `make`, `model`, `inventory_type`, `year`, `trim`, `version`, `base_exterior_color`, `exterior_color`, `base_interior_color`, `interior_color`, `seating_capacity`, `doors`, `engine`, `state`, `drivetrain`, `assembly_location`, `assembly_country`, `transmission`, `fuel_type`, `body_type`, `cylinders`, `dealer_type`, `availability_status`, `options_packages`, `features`, `keywords`, `price`, `msrp`, `miles`, and `days_on_market`.

Facet totals are returned at `data.total`. Categorical buckets are keyed by facet name under `data.facets`, numeric buckets are under `data.range_facets`, and numeric stats are under `data.stats`.

Facet buckets are sorted by count by default. Use `metric` to add one per-bucket aggregate while still returning `count` for sample-size context:

```sh theme={null}
curl "https://api.visor.vin/v1/facets?facets=trim&make=ford&model=f-150&metric=price.p95&sort=-metric" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Supported metric measures are `price`, `miles`, `msrp`, `days_on_market`, and `discount_from_msrp`. Supported metric aggregates are `mean`, `p5`, `p25`, `median`, `p75`, and `p95`. Non-count metrics require exactly one categorical facet. Numeric range facets such as `price`, `miles`, `msrp`, and `days_on_market` keep their range-bucket behavior.

Facet `sort` accepts `-count`, `count`, `-metric`, and `metric`. It defaults to `-count`, even when `metric` is supplied. Buckets with fewer than 10 matching listings return `metric.value: null` with `null_reason: insufficient_sample`.

```json theme={null}
{
  "data": {
    "total": 1234,
    "facets": {
      "make": [{ "value": "Toyota", "count": 1234 }],
      "model": [{ "value": "Camry", "count": 456 }]
    },
    "range_facets": {
      "price": {
        "buckets": [{ "min": 20000, "max": 25000, "count": 42 }],
        "interval": 5000,
        "min": 20000,
        "max": 25000
      }
    },
    "stats": {
      "price": { "min": 12000, "max": 65000, "count": 1200, "missing": 34, "mean": 31250, "median": 29995, "stddev": 8100 }
    }
  },
  "meta": { "facets": ["make", "model", "price"] }
}
```

## VIN Lookup

```sh theme={null}
curl "https://api.visor.vin/v1/vins/1HGCM82633A004352" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

VIN detail returns vehicle-level fields with the latest known listing nested under `latest_listing`. Dealer and photos are included on the latest listing by default. Request `include=price_history` for listing price changes and `include=options` for installed options in the build:

```sh theme={null}
curl "https://api.visor.vin/v1/vins/1HGCM82633A004352?include=price_history,options" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Unknown VINs return `404 not_found_error`; known VINs can return `status: "missing"` when they no longer appear in active inventory but have not been classified as sold.

With `include=price_history`, VIN price changes are returned at `data.latest_listing.price_history`. With `include=options`, installed options are returned at `data.build.options`. When supported provider data is available, the latest listing's pricing detail is returned at `data.latest_listing.pricing`; otherwise it is `null`.

## Dealers

Search dealers:

```sh theme={null}
curl "https://api.visor.vin/v1/dealers?state=CA,TX&type=franchise&make=honda&q=honda&limit=10" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Dealer search returns public dealer profiles with current active `listing_count`. Results include location summary fields, latitude/longitude, dealer `type`, normalized `website`, and represented franchise `makes`. Use `dealer_id`, `state`, `country`, `type`, `make`, and `q` to narrow results. The `dealer_id` filter accepts up to 100 comma-separated dealer IDs for direct profile lookup. The `make` filter matches represented franchise makes, not current inventory makes.

`listing_count` uses VIN-level listing deduplication and ownership assignment, so it can be lower than a raw dealer feed count when duplicate VIN evidence is attributed to another dealer.

Fetch one dealer:

```sh theme={null}
curl "https://api.visor.vin/v1/dealers/00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Dealer detail adds `phone` and structured `address` when available. Dealer profiles can exist with `listing_count: 0`.

List inventory for one dealer:

```sh theme={null}
curl "https://api.visor.vin/v1/dealers/00000000-0000-0000-0000-000000000000/listings?limit=10" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

Missing dealer detail records return `404 not_found_error`.

Use `/v1/dealers/{dealer_id}/listings` for single-dealer inventory. Use `/v1/listings?dealer_id=id1,id2` for explicit multi-dealer listing search across up to 50 dealer IDs.

Dealer inventory endpoints return listings attributed to that dealer after VIN-level deduplication. A VIN observed in a dealer's feed may be omitted when Freeway believes another dealer owns that listing.

## Pagination

Collection endpoints use `limit` and `offset`. The default `limit` is 50 and the maximum is 100. Values above 100 are rejected with `validation_error`; they are not silently clamped.

```json theme={null}
{
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1234,
    "next_offset": 50
  }
}
```

Use `next_offset` for the next page. It is `null` when the current page is the last page. `total` is counted for the same filters at request time and can change as inventory changes.

## Usage And Billing Headers

Successful paid responses include:

```txt theme={null}
X-Usage-Class: listing_search
X-Usage-Cost-Micros: 2000
X-Pricing-Version: 00000000-0000-0000-0000-000000000201
```

`X-Usage-Cost-Micros` is denominated in integer USD micros. `1000000` micros equals `1.00` USD. Postgres is the billing ledger authority; analytics exports may lag.

Successful `/v1` responses also include account-level rate-limit headers:

```txt theme={null}
X-RateLimit-Tier: tier_1
X-RateLimit-Limit-10s: 20
X-RateLimit-Remaining-10s: 19
X-RateLimit-Limit-60s: 60
X-RateLimit-Remaining-60s: 59
```

## Usage Summaries

Use `/v1/usage` to fetch daily billable usage buckets for the authenticated API account. This endpoint is not billable, requires the `usage.read` key scope, and is eventually consistent with the billing ledger.

```sh theme={null}
curl "https://api.visor.vin/v1/usage?start_date=2026-05-01&end_date=2026-05-10" \
  -H "Authorization: Bearer $VISOR_API_KEY"
```

```json theme={null}
{
  "data": [
    { "date": "2026-05-01", "metering_class": "listing_search", "requests": 120, "charged_micros": 240000 }
  ],
  "totals": { "requests": 120, "charged_micros": 240000 },
  "meta": {
    "start_date": "2026-05-01",
    "end_date": "2026-05-10",
    "interval": "day",
    "currency": "USD",
    "source": "public_api_usage_events",
    "freshness": "Usage analytics are eventually consistent with the billing ledger."
  }
}
```

The default range is the last 30 UTC days ending today. The maximum range is 366 days. Use `metering_class=listing_search,vehicle_detail` to filter the summary to specific usage classes.

## Pricing

Pre-production billing uses prepaid dollar-denominated API credits. Design partner credits are granted during onboarding; no monthly platform fee or public volume discounts apply during pre-production. High-volume usage and bulk data licensing are handled on custom terms.

Prices are per successful request, not per returned row. Collection endpoints can return up to 100 records per request. Successful zero-result searches are billable. Detail `404 not_found_error` responses are not billable.

| API usage type                            | Usage class                                 | Price/request |
| ----------------------------------------- | ------------------------------------------- | ------------: |
| Listing search                            | `listing_search`                            |      `$0.002` |
| Historical listing search                 | `listing_search_historical`                 |      `$0.004` |
| Dealer-filtered listing search            | `listing_search_dealer_filtered`            |      `$0.003` |
| Historical dealer-filtered listing search | `listing_search_dealer_filtered_historical` |      `$0.006` |
| Dealer inventory search                   | `dealer_inventory_search`                   |      `$0.003` |
| Historical dealer inventory search        | `dealer_inventory_search_historical`        |      `$0.006` |
| Enriched listing search                   | `listing_search_enriched`                   |      `$0.040` |
| Facet counts                              | `facet_counts`                              |      `$0.003` |
| Historical facet counts                   | `facet_counts_historical`                   |      `$0.006` |
| Vehicle detail                            | `vehicle_detail`                            |      `$0.003` |
| Enriched vehicle detail                   | `vehicle_detail_enriched`                   |      `$0.008` |
| Dealer lookup                             | `dealer_lookup`                             |      `$0.001` |

Historical pricing applies when a listing, dealer inventory, or facet request uses `inventory_status=sold`, `sold_within_days`, or `snapshot_date`.

Dealer-filtered listing pricing applies when `/v1/listings` uses the `dealer_id` filter. Pricing remains per successful page, not per dealer ID.

Enriched listing search pricing applies to listing collection requests with `include=price_history` and/or `include=options`. Pricing remains per successful page, not per listing, option, or price change event.

Enriched vehicle detail pricing applies to `GET /v1/vins/{vin}` and `GET /v1/listings/{listing_id}` when `include=price_history` and/or `include=options` is requested.

## Rate Limits

The beta public API enforces Usage Tier rate limits at the API account level. Limits are shared by all API keys on the account, so creating more keys does not increase throughput.

| Usage Tier | Requests / 10 seconds | Requests / 60 seconds |
| ---------- | --------------------: | --------------------: |
| Tier 1     |                    20 |                    60 |
| Tier 2     |                   100 |                   100 |
| Tier 3     |                   200 |                   600 |
| Tier 4     |                   500 |                  3000 |

All API accounts start in Tier 1. Automatic tier graduation uses verified paid API credits, days since first successful paid credit, and good standing. Admin or promotional beta credits do not count.

| Target Tier | Verified paid API credits | Days since first successful paid credit | Review                          |
| ----------- | ------------------------: | --------------------------------------: | ------------------------------- |
| Tier 2      |                     `$25` |                                       7 | Automatic when in good standing |
| Tier 3      |                    `$150` |                                      14 | Automatic when in good standing |
| Tier 4      |                    `$500` |                                      30 | Manual review required          |

Good standing means the API account is active, billing state is active or low balance, available balance is positive, and there has been no recent chargeback reversal. Your account dashboard is the source of truth for your current effective limits, including any account-specific override.

Rate-limited responses use HTTP `429` and include `Retry-After` when available. See [Usage Tiers](/docs/usage-tiers) for upgrade behavior and limit semantics.

## Error Examples

`400 validation_error`:

```json theme={null}
{
  "error": {
    "type": "validation_error",
    "code": "unknown_query_parameter",
    "message": "Unknown query parameter: bad_filter."
  },
  "_tag": "PublicApiValidationError"
}
```

`401 authentication_error`:

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "missing_api_key",
    "message": "Missing API key."
  },
  "_tag": "PublicApiAuthenticationError"
}
```

`402 billing_error`:

```json theme={null}
{
  "error": {
    "type": "billing_error",
    "code": "billing_state_blocked",
    "message": "Your API account cannot make requests in its current billing state."
  },
  "_tag": "PublicApiBillingError"
}
```

`404 not_found_error`:

```json theme={null}
{
  "error": {
    "type": "not_found_error",
    "code": "not_found",
    "message": "VIN was not found."
  },
  "_tag": "PublicApiNotFoundError"
}
```

`429 rate_limit_error`:

```json theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded."
  },
  "_tag": "PublicApiRateLimitError"
}
```

`503 platform_error`:

```json theme={null}
{
  "error": {
    "type": "platform_error",
    "code": "metering_unavailable",
    "message": "Usage metering is unavailable for this request."
  },
  "_tag": "PublicApiPlatformError"
}
```
