Stock
El lote publicado de una automotora — qué autos entran, qué campos trae cada uno, cómo se filtra y qué nunca sale al público.
Tres endpoints de lectura sobre una misma cosa: el lote público de una automotora.
curl https://api.vitrinadev.com/api/v1/stock \
-H "Authorization: Bearer $VITRINA_KEY"Es la superficie que consume el sitio web de la automotora. No es el inventario: el inventario está en /vehicles, con las patentes, los costos y los pisos de precio, y pide permisos que un sitio web no debería tener.
Qué autos entran al lote
Un auto aparece acá si está activo, no fue borrado, no fue fusionado con otro y no está vendido.
Lo que sorprende de esa lista es lo que no contiene: no hay ninguna condición de "publicado en un portal". El sitio propio de la automotora muestra todo su lote activo, no solo los autos que además están cruzados a Chileautos, Yapo o Mercado Libre. published_at existe como dato y sirve para ordenar, pero no filtra nada.
Los reservados sí aparecen, con status: "reservado" y su reserved_at, para que puedas pintarles la etiqueta en vez de hacerlos desaparecer. Los vendidos no aparecen, salvo que la automotora haya pedido expresamente que sus ventas recientes se queden a la vista.
El objeto
Así viene un auto, completo, tal como lo devolvió GET /stock/{id}:
{
"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
}
}Algunas cosas que no se deducen mirándolo:
Los pares *_type y *_label no son redundantes. El primero es el valor tal como lo guardó la automotora o lo entregó su feed; el segundo es la etiqueta en español lista para mostrar. Cuando coinciden es porque la automotora ya escribió el valor canónico. Si vas a renderizar, usa *_label; si vas a agrupar o comparar, usa *_type.
price.amount es un entero en la moneda de price.currency, sin decimales ni separadores. En pesos chilenos, 13990000 son 13.990.000.
sucursal viene embebida: no tienes que pedir la sucursal por separado para mostrar dónde está el auto. Trae comuna_name ya resuelto desde el código CUT.
photos es una lista ordenada por order; la primera es la portada.
Trampa
No pidas la patente: no está
El objeto público no trae registration_number ni vin, y no hay parámetro
que los agregue. Los sitios de clasificados chilenos casi nunca muestran la
patente, y una API pública no debe filtrarla. Tampoco salen los precios
internos, las etiquetas de fuente, ni de quién es el auto — si estás
construyendo algo que necesita eso, el recurso es /vehicles, no éste.
Filtrar
Todos los filtros son parámetros de query y se combinan con Y lógico.
| Parámetro | Qué hace |
|---|---|
brand, model | Marca y modelo. |
body | Carrocería. |
type | Tipo de unidad: Car, Truck, Motorcycle o Boat. |
gearbox, fuel_type | Transmisión y combustible. |
min_year, max_year | Rango de año. |
min_price, max_price | Rango de precio, en la moneda del lote. |
min_odometer, max_odometer | Rango de kilometraje. |
created_from, created_to | Rango de fecha de carga. |
sucursal | El uuid de una sucursal. |
status | disponible, reservado o vendido. |
featured | Solo los destacados que eligió la automotora. |
curl "https://api.vitrinadev.com/api/v1/stock?body=SUV&max_price=17000000&sort=price_asc" \
-H "Authorization: Bearer $VITRINA_KEY"De mis tres autos, ese filtro devolvió uno: el Hyundai Tucson, a 15.200.000. El Mazda CX-5 también es SUV, pero cuesta 18.490.000.
sort acepta price_asc, price_desc, created_asc, created_desc, published_asc, published_desc y featured_desc. Sin sort, el orden es created_desc: lo último cargado primero.
gearbox, fuel_type y body aceptan tanto el código canónico como la etiqueta en español. Probé los dos y devuelven las mismas filas:
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"No pasa lo mismo con type, que sí es un enum cerrado y rechaza lo que no reconoce:
{
"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"
}
}Trampa
`status=vendido` es gramática válida, no una puerta
El filtro se acepta, pero se aplica encima del lote público, no en lugar de
él. Si la automotora no pidió mostrar sus ventas recientes, la consulta no
devuelve nada — en mi lote, GET /stock/count?status=vendido devolvió
{ "data": { "count": 0 } } con tres autos cargados. Qué se muestra lo decide
la automotora, nunca quien llama, y por eso tampoco existe un parámetro
include_sold.
Contar
Cuando solo necesitas el número — el "12 autos disponibles" de un encabezado —, hay un endpoint que no trae los autos:
curl https://api.vitrinadev.com/api/v1/stock/count \
-H "Authorization: Bearer $VITRINA_KEY"{ "data": { "count": 3 } }Acepta exactamente los mismos filtros que la lista, así que GET /stock/count?status=disponible cuenta lo mismo que contarías paginando — en mi lote, 2 de 3, porque el Tucson estaba reservado.
Paginar
limit y offset, no cursores.
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 devuelve los valores efectivos, no los que pediste. El limit por defecto es 20 y el máximo es 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 viene un total. Si necesitas saber cuántas páginas hay, pide GET /stock/count con los mismos filtros.
Una unidad
curl https://api.vitrinadev.com/api/v1/stock/08eecaca-10bc-40b0-a2eb-d0915dba1cc9 \
-H "Authorization: Bearer $VITRINA_KEY"Devuelve el mismo objeto de la lista. Un id que no existe, o uno que existe pero está fuera del lote público, responde igual:
{
"error": {
"code": "NOT_FOUND",
"message": "Vehículo no encontrado",
"requestId": "e3c36709-4fff-462d-8940-4ff9e8138ce6"
}
}Los dos casos son un 404 y no se distinguen desde afuera, a propósito: que un auto exista en el inventario de la automotora no es información pública.
Un id que ni siquiera es un uuid falla antes, con 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"
}
}Lo que sigue
Autenticación y API keys
Qué es una key sk_, qué decide un scope, cómo se emite y se revoca una credencial, y cuántas llamadas por minuto aguanta.
Sucursales
Crear, leer, corregir y desactivar las sucursales de una automotora — con el código de comuna que las ancla y lo que pasa con los autos cuando una se apaga.