VitrinaAPI

Load and keep the lot

Which cars enter the public lot, what each one carries and how to filter.

Car dealershipsOnly in car-dealership workspaces.

GET /stock returns a dealership's public lot. Three read endpoints sit over that same surface.

curl https://api.vitrinadev.com/api/v1/stock \
  -H "Authorization: Bearer $VITRINA_KEY"

It's the surface a dealership's website consumes. The inventory is something else, and it lives at /vehicles. That's where the plates, the costs and the price floors are, behind permissions a website shouldn't hold.

Which cars enter the lot

A car shows up here if it is active, was not deleted, was not merged into another one, and is not sold.

What that list leaves out matters more. There's no "published on a portal" condition anywhere in it. A dealership's own site shows its whole active lot, including the cars that are not cross-posted to Chileautos, Yapo or Mercado Libre. published_at exists as data and is useful for ordering, but it filters nothing.

Reserved cars do appear, with status: "reservado" and their reserved_at, so you can paint the badge instead of making them vanish. Sold ones stay out, unless the dealership has explicitly asked for its recent sales to stay visible.

The object

This is how a car arrives, whole, exactly as GET /stock/{id} returned it:

{
  "data": {
    "id": "08eecaca-10bc-40b0-a2eb-d0915dba1cc9",
    "make": "Toyota",
    "model": "Corolla",
    "version": "1.8 XEI CVT",
    "year": 2022,
    "title": null,
    "price": { "amount": 13990000, "currency": "CLP" },
    "odometer": { "value": 42500, "unit": "KM" },
    "fuel_type": "Bencina",
    "fuel_label": "Bencina",
    "gear_type": "Automática",
    "gear_label": "Automática",
    "body_style": "Sedán",
    "body_label": "Sedán",
    "color": "Gris",
    "doors": 4,
    "displacement_cc": null,
    "type": "Car",
    "type_label": "Auto",
    "merch_label": null,
    "merch_label_es": null,
    "is_featured": false,
    "featured_at": null,
    "listing_type": "Usado",
    "status": "disponible",
    "reserved_at": null,
    "sold_at": null,
    "sucursal": {
      "id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
      "name": "Sucursal Providencia",
      "address": null,
      "address_street": "Av. Nueva Providencia",
      "address_number": "2214",
      "address_unit": null,
      "comuna_code": "13123",
      "comuna_name": "Providencia",
      "region_code": "13"
    },
    "photos": [{ "url": "https://images.example.cl/corolla-2022-1.jpg", "order": 0 }],
    "description": "Mantenciones al día en concesionario.",
    "tags": [],
    "equipment": [],
    "created_at": "2026-09-15T18:34:48.575Z",
    "published_at": null
  }
}

The labels the API returns are Spanish: Bencina, Automática, Sedán, Usado, disponible. They're stored data rather than UI copy, and the API hands them back as they are.

A few things you cannot work out by looking at it:

The *_type and *_label pairs are not redundant. The first is the value as the dealership stored it or as its feed delivered it; the second is the Spanish label, ready to display. When they match it is because the dealership already wrote the canonical value. If you are rendering, use *_label; if you are grouping or comparing, use *_type.

price.amount is an integer in the currency of price.currency, with no decimals and no separators. In Chilean pesos, 13990000 is CLP 13,990,000, written 13.990.000 locally.

sucursal is embedded: you do not have to fetch the location separately to show where the car is. It carries comuna_name already resolved from the CUT code. The field is named in Spanish because it is part of the wire contract, and the wire contract is not translated.

photos travels exactly as the dealership stored it, with each photo's order inside it. The API does not reorder it, so sort it yourself by order before picking the cover.

Trap

Do not ask for the plate: it is not there

The public object carries neither registration_number nor vin, and there's no parameter that adds them. Internal prices, the source and who owns the car are out too. If you need that data, the resource is /vehicles.

Filtering

All the filters are query parameters, and they combine with a logical AND.

ParameterWhat it does
brand, modelMake and model.
bodyBody style.
typeUnit type: Car, Truck, Motorcycle or Boat.
gearbox, fuel_typeTransmission and fuel.
min_year, max_yearYear range.
min_price, max_pricePrice range, in the lot's currency.
min_odometer, max_odometerMileage range.
created_from, created_toRange on the date the car was loaded.
sucursalA location's uuid.
statusdisponible, reservado or vendido.
featuredOnly the ones the dealership marked as featured.
curl "https://api.vitrinadev.com/api/v1/stock?body=SUV&max_price=17000000&sort=price_asc" \
  -H "Authorization: Bearer $VITRINA_KEY"

Across a lot of three cars, that filter returned one: the Hyundai Tucson, at CLP 15,200,000. The Mazda CX-5 is an SUV too, but it costs CLP 18,490,000.

sort accepts price_asc, price_desc, created_asc, created_desc, published_asc, published_desc and featured_desc. Without sort, the order is created_desc: last loaded first.

gearbox, fuel_type and body accept both the canonical code and the Spanish label. Either spelling returns the same rows:

curl "https://api.vitrinadev.com/api/v1/stock?gearbox=automatica" -H "Authorization: Bearer $VITRINA_KEY"
curl "https://api.vitrinadev.com/api/v1/stock?gearbox=Autom%C3%A1tica" -H "Authorization: Bearer $VITRINA_KEY"

The same is not true of type, which is a closed enum and rejects what it does not recognise:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": { "query": [{ "path": "type", "message": "Invalid enum value. Expected 'Car' | 'Truck' | 'Motorcycle' | 'Boat', received 'Bus'", "code": "invalid_enum_value" }] },
    "requestId": "7b0841cd-0345-4cb5-a2fb-3dbb4ad4285c"
  }
}

Trap

status=vendido is accepted and returns nothing

The filter is applied on top of the public lot. If the dealership hasn't asked for its recent sales to be shown, the query returns nothing. What gets shown is the dealership's decision, not the caller's, and there's no include_sold parameter.

Counting

For the "12 cars available" that sits above the grid, there's an endpoint that doesn't bring the cars:

curl https://api.vitrinadev.com/api/v1/stock/count \
  -H "Authorization: Bearer $VITRINA_KEY"
{ "data": { "count": 3 } }

It takes exactly the same filters as the list. GET /stock/count?status=disponible counts what you'd count by paginating: across that lot of three, it returned 2, because the Tucson was reserved.

Paginating

limit and offset, not cursors.

curl "https://api.vitrinadev.com/api/v1/stock?limit=1&offset=1" \
  -H "Authorization: Bearer $VITRINA_KEY"
{
  "data": [ { "make": "Mazda", "model": "CX-5" } ],
  "meta": { "pagination": { "limit": 1, "offset": 1 } }
}

meta.pagination returns the effective values, not the ones you asked for. The default limit is 20 and the ceiling is 100:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": { "query": [{ "path": "limit", "message": "Number must be less than or equal to 100", "code": "too_big" }] },
    "requestId": "278945a0-d78d-470c-ad5c-86b029724a96"
  }
}

No total comes back. To know how many pages there are, ask GET /stock/count with the same filters.

One unit

curl https://api.vitrinadev.com/api/v1/stock/08eecaca-10bc-40b0-a2eb-d0915dba1cc9 \
  -H "Authorization: Bearer $VITRINA_KEY"

It returns the same object as the list. An id that does not exist, and one that exists but sits outside the public lot, answer the same way:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Vehículo no encontrado",
    "requestId": "e3c36709-4fff-462d-8940-4ff9e8138ce6"
  }
}

Both cases are a 404 and can't be told apart from outside. Whether a car exists in a dealership's inventory isn't public information.

An id that isn't even a uuid fails earlier, with 400:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": { "params": [{ "path": "id", "message": "Invalid uuid", "code": "invalid_string" }] },
    "requestId": "6c0a7f7f-3147-4b29-8ce0-a7548c03aa51"
  }
}

The sucursal block comes from Locations, and the contract for all three endpoints is in Reference · Public stock.

On this page