---
title: "CarGurus API: Search Used Car Listings and Dealers"
description: "Get CarGurus used car listings as JSON: look up make and model IDs, run search at 1 credit a call, then pull a dealer by service_provider_id."
url: https://perceptlayer.com/blog/cargurus-api-used-car-search
---

[Blog](https://perceptlayer.com/blog) / [API guides](https://perceptlayer.com/blog/category/api-guides)

# CarGurus API: Search Used Car Listings and Dealers

4 Aug 2026 Perceptlayer 8 min read

The CarGurus API on Perceptlayer has four endpoints: `makes`, `models`, `search` and `dealer`. Each is a GET request with an `X-API-Key` header, each costs 1 credit on success, and each returns JSON. You look up a make ID, then a model ID, pass them to `search` to get listings, and use the `service_provider_id` from any listing to fetch its dealer.

## Key takeaways

-   Four endpoints cover the flow: `/v1/cargurus/makes`, `/v1/cargurus/models`, `/v1/cargurus/search` and `/v1/cargurus/dealer`, at 1 credit per successful call.
-   `search` filters by `make_model_trim_paths`, such as `m26/d126` for the GMC Suburban in the recorded response, so fetch those IDs from `makes` and `models` first.
-   Each listing carries `price`, `mileage`, `vin`, `days_on_market` and a `seller` object whose `service_provider_id` feeds the `dealer` endpoint.
-   Budget a regional pull as lookups plus (models × pages) search calls; failed calls cost 0 credits.

## What does the CarGurus API return?

The CarGurus data on Perceptlayer is structured used and new car inventory: one listing per vehicle, with the year, make, model, trim, price, mileage, VIN, seller and a link back to the listing page. The [CarGurus API page](https://perceptlayer.com/apis/cargurus) lists all four endpoints, and the playground there lets you run a real call with your key before you write any code.

This guide follows the order you'd use in practice. Makes and models give you valid inputs, search gives you listings, and dealer gives you the seller behind a listing. The recorded response below comes from a real search call, so every field name you see is one the API returned.

The endpoints sit in the [Cars area](https://perceptlayer.com/verticals/cars) of the catalogue. If you want to know which listing fields suit price analysis, [used car listing data: fields for price analysis](https://perceptlayer.com/blog/used-car-listing-data-price-analysis) covers that question separately.

## Which endpoints does the CarGurus API have?

The CarGurus API has four endpoints: `makes`, `models`, `search` and `dealer`. All are `GET` requests and each costs 1 credit on success. Only `makes` and `search` take no required parameter; `models` needs a `make_id` and `dealer` needs a `service_provider_id`.

| Endpoint | Returns | Credits | Required | Optional |
| --- | --- | --- | --- | --- |
| [`/v1/cargurus/makes`](https://perceptlayer.com/apis/cargurus/makes) | Makes | 1 | none | `locale`, `country_code`, `origin`, `device_platform` |
| [`/v1/cargurus/models`](https://perceptlayer.com/apis/cargurus/models) | Models | 1 | `make_id` | `locale`, `country_code`, `origin`, `device_platform` |
| [`/v1/cargurus/search`](https://perceptlayer.com/apis/cargurus/search) | Search | 1 | none | `make_model_trim_paths`, `price_min`, `price_max`, `distance`, `sort`, `sort_direction`, `page` |
| [`/v1/cargurus/dealer`](https://perceptlayer.com/apis/cargurus/dealer) | Dealer | 1 | `service_provider_id` | none |

Authentication is the `X-API-Key` header with a key that starts with `sk_live_`. Where a header isn't possible, the `api_key` query parameter works instead. The [authentication docs](https://docs.perceptlayer.com/authentication) cover both.

## How do you get valid make and model IDs?

Call `makes` first, then `models` with a `make_id` taken from the makes response. Search filters on IDs, not names, so this lookup is how you avoid guessing. In the recorded search response, the GMC Suburban appears as `make_id` `m26` and `model_id` `d126`.

```bash
curl "https://api.perceptlayer.com/v1/cargurus/makes" \
  -H "X-API-Key: $API_KEY"
```

Then pass one of the returned make IDs to `models`:

```bash
curl "https://api.perceptlayer.com/v1/cargurus/models?make_id=m26" \
  -H "X-API-Key: $API_KEY"
```

We haven't recorded a makes or models response for this guide, so we won't show one. Run both calls in the playground on the [Makes](https://perceptlayer.com/apis/cargurus/makes) and [Models](https://perceptlayer.com/apis/cargurus/models) pages to see the exact fields your key returns.

Both lookups also accept `locale`, `country_code`, `origin` and `device_platform`. They're optional strings, up to 20 characters for `locale` and 40 for the others. Nothing more is documented about them, so leave them out unless you've tested a value in the playground.

The IDs are inputs you reuse on every search, so cache them instead of repeating the lookups. [Cache API responses in Python](https://perceptlayer.com/blog/cache-web-data-api-responses) shows a TTL approach that fits.

## How do you search used car listings?

To search used car listings, call `/v1/cargurus/search` with `make_model_trim_paths` set to the make ID and model ID joined by a slash, URL-encoded as `%2F`. Add `distance`, price bounds and a sort order to narrow the results. Each successful call costs 1 credit and returns one page of listings. This is the request we recorded:

```bash
curl "https://api.perceptlayer.com/v1/cargurus/search?make_model_trim_paths=m26%2Fd126&distance=50000&sort=best_match" \
  -H "X-API-Key: $API_KEY"
```

The same request in Python:

```python
import os
import requests

resp = requests.get(
    "https://api.perceptlayer.com/v1/cargurus/search",
    headers={"X-API-Key": os.environ["API_KEY"]},
    params={
        "make_model_trim_paths": "m26/d126",
        "distance": 50000,
        "sort": "best_match",
    },
    timeout=30,
)
resp.raise_for_status()
data = resp.json()["data"]
for listing in data["listings"]:
    print(listing["listing_title"], listing["price"]["amount"], listing["mileage"]["value"])
```

`requests` encodes the slash for you. The response has `data.listings`, `data.pagination` and a `meta` object.

### Which parameters does search take?

All seven are optional. Nothing about defaults or page sizes is documented, so the table lists only types and allowed values.

| Parameter | Type | Notes |
| --- | --- | --- |
| `make_model_trim_paths` | string | Up to 200 characters. Example: `m26/d126` |
| `price_min` | integer | Lower price bound |
| `price_max` | integer | Upper price bound |
| `distance` | integer | Search radius |
| `sort` | string | One of `best_match`, `deal_score`, `price`, `mileage`, `distance`, `date` |
| `sort_direction` | string | One of `asc`, `desc` |
| `page` | integer | Page number |

## What does a search response look like?

A search response has `data.listings`, `data.pagination` and a `meta` object, and each entry in `data.listings` is one vehicle with its price, mileage, VIN and seller. The excerpt below is trimmed from the recorded response for the request above, with `…` marking the cut parts.

```json
{
  "success": true,
  "data": {
    "listings": [
      {
        "listing_id": 461194930,
        "listing_title": "1996 GMC Suburban",
        "listing_url": "https://www.cargurus.com/details/461194930",
        "vin": "3GKFK16R5TG503480",
        "year": 1996,
        "make": "GMC",
        "make_id": "m26",
        "model": "Suburban",
        "model_id": "d126",
        "trim": "K1500 4WD",
        "price": {
          "amount": 7995,
          "currency": "USD",
          "localized": "$7,995",
          "rating": "NA",
          "fees_compliant": true
        },
        "mileage": {
          "value": 175456,
          "unit": "MILES",
          "localized": "175,456"
        },
        "drivetrain": "Four-Wheel Drive",
        "fuel_type": "Gasoline",
        "days_on_market": 14,
        "distance": 537.75244,
        "seller": {
          "service_provider_id": "349337",
          "name": "Little Rock Auto Sales",
          "city": "Charlotte",
          "region": "NC",
          "location": "Charlotte, NC",
          …
        },
        "is_new": false,
        "is_cpo": false,
        "seller_type": "DEALER"
      },
      …
    ],
    "pagination": {
      "page": 1,
      "page_size": 21,
      "page_count": 1,
      "total": 21,
      "has_more": false
    }
  },
  "meta": {
    "duration_ms": 1556,
    "endpoint_family": "search",
    "market": "US"
  }
}
```

A few fields worth knowing, all from the recording:

-   `price.amount` is a number and `price.localized` is the display string. Use the number for analysis.
-   `mileage.value` pairs with `mileage.unit`, which was `MILES` here.
-   `vin` identifies the vehicle, and `listing_id` identifies the listing. Pick the key that matches your question: vehicle or advert.
-   Some fields appear on one listing and not another. The second listing in the recording has `exterior_color`, `interior_color` and an image `count`, while the first doesn't. Treat optional fields as optional in your code.
-   `pagination.has_more` tells you whether to request the next `page`.

## How do you look up a dealer?

To look up a dealer, take `seller.service_provider_id` from a listing and pass it to `/v1/cargurus/dealer`, which costs 1 credit on success. The dealer ID in the recorded response is `349337`, for the Charlotte, NC seller above. The parameter is a string of up to 20 characters.

```bash
curl "https://api.perceptlayer.com/v1/cargurus/dealer?service_provider_id=349337" \
  -H "X-API-Key: $API_KEY"
```

We haven't recorded a dealer response, so the fields it returns aren't shown here. The [Dealer endpoint page](https://perceptlayer.com/apis/cargurus/dealer) lists it and its playground runs the call.

The listing already includes the seller's `name`, `city`, `region` and `postal_code`, so you only need the dealer call when you want the dealer record itself. Collect the distinct `service_provider_id` values from your search results first and call `dealer` once per ID, not once per listing.

## What does a regional inventory pull cost?

A regional inventory pull costs one credit per successful call, so the cost is the call count: the lookups, plus one search call per page per model, plus one dealer call per distinct dealer. Failed calls aren't billed. The numbers below are an illustration for planning, not a measurement.

Say you track 40 models. The lookups are one `makes` call, plus one `models` call for each make involved. Take 10 makes: 1 + 10 = 11 credits, and you cache them.

Then search each model. If each model's results fit on 3 pages, that's 40 models × 3 pages = 120 credits per full pass. Add 25 distinct dealers at 1 credit each: 25 credits. One refresh is 120 + 25 = 145 credits.

Run that daily for 30 days: 30 × 145 = 4,350 credits. The free tier is 1,000 credits a month, and credits are prepaid in packs starting at 10,000, which last 12 months. The [pricing page](https://perceptlayer.com/pricing) has what credits cost, and the [credits docs](https://docs.perceptlayer.com/credits) explain how a call is debited.

Your own page count will differ. In the recorded response, the whole result fit on one page, with `page_count` 1 and `total` 21. Run a model once and read `pagination.page_count` before you multiply.

Failed calls cost nothing and return `X-Credits-Used: 0`. Successful responses carry `X-Credits-Used` and `X-Credits-Remaining`, so you can log the balance as a job runs.

## How do you page through results safely?

To page through results safely, request `page` 1, read `pagination.has_more`, and keep going until it's false. Back off when you get a 429, using the `Retry-After` header, and don't retry blindly after a client timeout. Cap the number of pages per run so a bug can't spend credits without limit.

```python
import os
import time
import requests

URL = "https://api.perceptlayer.com/v1/cargurus/search"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}

def fetch_all(path, max_pages=20):
    listings = []
    for page in range(1, max_pages + 1):
        while True:
            r = requests.get(
                URL,
                headers=HEADERS,
                params={"make_model_trim_paths": path, "page": page},
                timeout=30,
            )
            if r.status_code in (429, 503):
                time.sleep(int(r.headers.get("Retry-After", "5")))
                continue
            break
        r.raise_for_status()
        data = r.json()["data"]
        listings.extend(data["listings"])
        if not data["pagination"]["has_more"]:
            break
    return listings

cars = fetch_all("m26/d126")
print(len(cars))
```

`max_pages` caps the spend of one run. Requests have no idempotency key, so a call whose reply was lost may have succeeded and been billed. [Retry web API calls without paying twice](https://perceptlayer.com/blog/retry-web-api-calls-without-paying-twice) covers that, and [paginate a web data API in Python](https://perceptlayer.com/blog/paginate-web-data-api-python) goes deeper on loops.

Rate limits are per account and counted per minute. Free accounts get 1 request a second, and paid packs include faster rates. See the [rate limits docs](https://docs.perceptlayer.com/rate-limits) and [error codes](https://docs.perceptlayer.com/errors) for the full list.

## What should you know before you build on it?

Data is collected from publicly available sources and returned as found at the time of the call. A listing can be sold or repriced after you fetch it, so treat a response as a snapshot and store the time you fetched it.

CarGurus is a marketplace for dealer inventory, and this API reads its listings. If you need a different region, [AutoScout24 covers European listings](https://perceptlayer.com/blog/autoscout24-api-car-listings-json). For your own inventory, your dealer management system is the system of record, so use it there rather than reading your own cars back from public listings.

For agents, the same endpoints are available as MCP tools. `search_endpoints` and `get_endpoint` only look up the catalogue and cost nothing; `call_endpoint` runs the call and spends 1 credit on success. The [AI agents docs](https://docs.perceptlayer.com/ai-agents) show the setup.

Ready to run it? [Get a free API key](https://perceptlayer.com/signup), send the `makes` call above, and follow the IDs through to `search`. You can browse the [CarGurus endpoints](https://perceptlayer.com/apis/cargurus) or the other [API guides](https://perceptlayer.com/blog/category/api-guides) from there. For background on the source, see [CarGurus](https://www.cargurus.com/), and for the header behind the backoff above, see the [`Retry-After` definition in RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#field.retry-after).

## APIs in this post

-   [CarGurus **1 credit per call** · 4 endpoints](https://perceptlayer.com/apis/cargurus)

## Frequently asked questions

### How much does the CarGurus API cost per call?

Each successful call to `makes`, `models`, `search` or `dealer` costs 1 credit. Failed calls cost nothing and return `X-Credits-Used: 0`. Every account gets 1,000 free credits a month after verifying an email, with no credit card. The [pricing page](https://perceptlayer.com/pricing) shows what credits cost.

### Can I search by price range?

Yes. `search` takes `price_min` and `price_max` as integers, and you can combine them with `distance`, `sort` and `sort_direction`. The `sort` parameter accepts `best_match`, `deal_score`, `price`, `mileage`, `distance` or `date`, and `sort_direction` accepts `asc` or `desc`.

### Where do I get the service\_provider\_id for the dealer endpoint?

Read it from `seller.service_provider_id` in any search listing. In the recorded response, one listing's seller has `service_provider_id` `349337`. Pass that string, up to 20 characters, to `/v1/cargurus/dealer`. Deduplicate IDs first so you pay for each dealer once.

### What happens if I hit the rate limit?

You get a 429 `rate_limited` response with a `Retry-After` header giving the seconds to wait, and the call isn't billed. Limits are per account, counted per minute. Free accounts get 1 request a second. Read `RateLimit-Remaining` to pace a loop before you hit the limit.

### Do I need a CarGurus account or key?

No. You need a Perceptlayer API key, sent in the `X-API-Key` header. Sign up for one free, and your 1,000 monthly credits cover about 1,000 calls at 1 credit each. The [quick start](https://docs.perceptlayer.com/quickstart) walks through the first request.

cars cargurus used cars listings dealers

## More from the blog

1.  [API guides](https://perceptlayer.com/blog/category/api-guides) 7 Aug 2026 Perceptlayer 7 min read
    
    ## [AutoScout24 API: Search European Car Listings as JSON](https://perceptlayer.com/blog/autoscout24-api-car-listings-json)
    
    Search AutoScout24 listings as JSON: make\_id and price filters, a recorded BMW response, and what a monthly price sweep costs in credits.
    
2.  [API guides](https://perceptlayer.com/blog/category/api-guides) 23 Oct 2026 Perceptlayer 7 min read
    
    ## [Cars.com API: Used Car Listings and Price Evaluations](https://perceptlayer.com/blog/cars-com-api-listings-price-evaluation)
    
    Search Cars.com listings with its six required parameters (zip, radius\_miles, stock\_type, sort, page, page\_size), then listing, dealer and price-eval calls.
    
3.  [API guides](https://perceptlayer.com/blog/category/api-guides) 15 Oct 2026 Perceptlayer 7 min read
    
    ## [Redfin API: Search Homes, Then Property and Valuation](https://perceptlayer.com/blog/redfin-api-property-valuation)
    
    Four Redfin endpoints at 1 credit each: find a region, search homes with a real JSON response, then call property and valuation by property\_id.
