Um guia de implementação escrito com base no comportamento documentado da API; não cita nenhum cliente. Publicado em .
O problema: o leilão começa antes das fotos
Um catálogo de leilão é publicado assim que os lotes consignados chegam: um VIN, um número de lote e uma data de venda para cada carro. As fotos vêm quando o carro chega ao pátio e o fotógrafo chega ao carro, o que, para parte de um leilão, acontece depois que os lances já começaram. Até lá, cada um desses lotes é uma caixa cinza ao lado de uma linha de texto.
Um render do catálogo preenche o espaço com o ano-modelo certo. Um leilão também é exatamente onde um render nunca pode se passar pelo carro: os licitantes precificam o estado de conservação, e um render não mostra nada disso, porque não retrata nenhum veículo físico específico. Tudo o que vem a seguir decorre de levar essas duas coisas em conta ao mesmo tempo.
Como fica a integração
- Um render por lote a partir do VIN, exibido só até chegarem as fotos do próprio lote.
- Um seletor de catálogo para os lotes cujo VIN não é decodificado.
- Um aviso na imagem: "Renderização do catálogo, não este lote".
- Uma URL por lote, gerada uma única vez com uma
Idempotency-Key, válida enquanto durar a venda.
Etapa 1: do VIN do lote a um id de veículo
GET /api/v1/vin/{vin} é gratuito. Quando o catálogo tem o veículo, a resposta traz data.vehicle.id, o id estável a ser armazenado no lote; a versão, a carroceria e o motor decodificados ficam no texto do lote. O catálogo vai de 1990 a 2027, então o estoque mais antigo de um leilão é renderizado como o mais novo. O guia de seguros explica uma decodificação campo a campo, e a documentação de VIN trata de VINs parciais e erros.
# the lot's VIN to the vehicle id the lot stores (free); nothing printed means no catalog match
npx @meterapp/car-image vin "$LOT_VIN" --json | jq -r '.data.vehicle.id // empty'Etapa 2: sem VIN decodificável? Escolha o veículo no catálogo
Alguns lotes chegam sem um VIN decodificável: um veículo fabricado para outro mercado, um VIN digitado errado na origem, uma ficha de consignação com ano, marca e modelo e nada mais. Coloque no formulário de consignação três menus suspensos alimentados por GET /api/v1/vehicles, que é gratuito e público: ?year= lista as marcas com algum modelo naquele ano, e ?year=&makeId= lista os modelos dessa marca, cada um com o id de veículo daquele ano-modelo.
# every make with a 2006 model year (free, public)
curl -s "https://carimage.dev/api/v1/vehicles?year=2006"
# {"data":{"year":2006,"makes":[…,{"id":476,"name":"DODGE","slug":"dodge"},…]},…}
# that make's 2006 models, each with the id of its 2006 model year
curl -s "https://carimage.dev/api/v1/vehicles?year=2006&makeId=476"{
"data": {
"year": 2006,
"make_id": 476,
"models": [
{
"name": "Caliber",
"slug": "caliber",
"vehicle_type": "Passenger Car",
"id": "veh_294vq81yq0d3y"
},
{
"name": "Charger",
"slug": "charger",
"vehicle_type": "Passenger Car",
"id": "veh_96j8z47jqfd4r"
},
"…"
]
}
}O mesmo catálogo é o pacote aberto @meterapp/vehicle-db, então os menus suspensos também podem funcionar offline com a mesma lista. De qualquer forma, o lote fica com o mesmo tipo de ID de um VIN decodificado, e nenhuma etapa seguinte precisa escrever o nome de um carro.
Quando uma planilha de consignação inteira chega de uma vez, POST /api/v1/vehicles/check lê até 2.000 entradas por chamada, cada uma escrita como está na planilha e com o número do lote como ref, e responde a todas: uma correspondência com o id a armazenar, sugestões, uma não correspondência ou uma linha inválida. Nada é renderizado e nada é cobrado; a documentação de vehicles traz os detalhes.
Etapa 3: identifique a renderização como renderização
Coloque o aviso na própria imagem, onde o olhar do licitante pousa, e no texto alternativo, não apenas na descrição abaixo. Estado, quilometragem, danos, opcionais e a pintura do carro pertencem ao texto do lote e, quando existirem, às fotos. Quando as fotos chegarem, a renderização sai.
<figure class="lot-image">
<img src="https://carimage.dev/api/v1/delivery/eyJhbGciOi…" width="800" height="450"
alt="Catalog render of a 2006 Dodge Charger, not a photo of lot 117">
<span class="lot-image__badge">Catalog render, not this lot</span>
</figure>
<p>Photos of this lot are on their way. Condition, mileage and damage are in the report.</p>Passo 4: uma URL por lote, gerada uma única vez
Emita a URL do lote no cadastro com POST /api/v1/image-urls e uma Idempotency-Key formada a partir do leilão e do lote, usando letras, dígitos, ., _, : e -. A URL é paga no momento em que é emitida (1 crédito), então, sem uma chave, um cadastro repetido emitiria e pagaria duas vezes. Com uma chave:
- A mesma chave com o mesmo corpo em até 24 horas reproduz a primeira resposta, incluindo a mesma URL, com
Idempotent-Replayed: truee sem uma segunda cobrança. - A mesma chave com um corpo diferente resulta em
422, e uma nova tentativa que ultrapassa a primeira requisição resulta em409com umRetry-After. Portanto, defina tudo no corpo, incluindo o tempo de validade, quando o lote for criado, e armazene-o junto com o lote. - Armazene também a URL no lote: a chave protege as novas tentativas, e a URL armazenada é o que as páginas leem pelo resto do leilão.
Um catálogo inteiro importado de uma vez pode gerar até 50 lotes por chamada, com uma chave por envio; cada URL na resposta repete o veículo e as opções para os quais foi gerada.
curl --fail-with-body -X POST https://carimage.dev/api/v1/image-urls \
-H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sale-2026-10-14:lot-117" \
-d '{"images":[{"vehicle":"veh_96j8z47jqfd4r","view":"side","width":800,"height":450,"fit":"contain","trim":true,"padding":6,"format":"auto"}],"ttl_seconds":259200}'
# run it again: the same URL comes back with Idempotent-Replayed: true, and nothing more is chargedPasso 5: ajuste o tempo de validade da URL ao leilão
ttl_seconds vai até 604800 (7 dias); um valor maior é limitado a esse máximo. Para um leilão que termina dentro da semana, defina-o como o tempo restante até o encerramento. Assim, a imagem provisória para de funcionar quando os lances terminam e, depois disso, a URL responde 403.
- Uma venda com prazo de mais de 7 dias: gere com
renew: true,ttl_secondsno máximo erenew_dayscobrindo toda a venda. Cada semana adicional em que o lote é aberto custa 1 crédito, semanas sem acesso não custam nada e a renovação termina emrenews_until. - Mantenha
max_usesem 0, o padrão: uma URL ilimitada pode ser armazenada em cache publicamente durante toda a sua validade, enquanto uma limitada éprivate, no-storee envia cada carregamento para a origem. - Restrinja a chave que gera as URLs aos domínios do seu leilão no painel: uma imagem de lote copiada para outro site responde
403.
import { CarImageClient, type ImageParams } from "@meterapp/car-image-sdk";
const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });
const lotImage = (vehicle: string): ImageParams => ({
vehicle, view: "side", width: 800, height: 450, fit: "contain", trim: true, padding: 6, format: "auto",
});
// How long the lot's URL lives, decided once from the sale's close.
function lifetime(closesAt: Date) {
const seconds = secondsUntil(closesAt);
return seconds <= 604800
? { ttlSeconds: Math.max(seconds, 60) } // closes within the week
: { ttlSeconds: 604800, renew: true, renewDays: Math.min(daysUntil(closesAt), 365) }; // a longer timed sale
}
// At intake, once per lot. The lifetime is stored with the lot, so a retry sends
// the same body and replays the first answer instead of minting a second URL.
lot.imageLifetime ??= lifetime(sale.closesAt);
const { data: [placeholder] } = await client.createImageUrls(lotImage(lot.vehicleId), {
...lot.imageLifetime,
idempotencyKey: `${sale.id}:lot-${lot.number}`,
});
lot.placeholderUrl = placeholder.url; // replaced by the lot's own photos when they arrivePlaneje conforme o tamanho do leilão
Cada lote é um veículo distinto, a menos que dois lotes compartilhem marca, modelo e ano, e cada plano limita os veículos distintos por mês: Free 100, Pro 2.500 e Business 15.000; Enterprise não tem limite. Um veículo já atendido neste mês é sempre permitido, e uma requisição em massa que ultrapassaria o limite é recusada por inteiro, antes de qualquer cobrança, com o plano e o limite na resposta. Dimensione o plano pelos lotes que você anuncia em um mês, não pelos créditos. A história do marketplace explica como espaçar uma importação grande dentro dos limites de taxa e de renderização.
Perguntas frequentes
- A renderização pode continuar no ar depois que as fotos do lote chegarem?
- Substitua. O render é um marcador do ano-modelo, e um licitante nunca deveria ter dúvida sobre qual imagem é o carro à venda. Se uma página o mantiver como referência, o rótulo vai junto.
- O que acontece com a URL depois da venda?
- Ela expira quando sua validade termina, ou quando uma URL renovável chega ao fim da renovação, e então responde 403. Até lá, o lote já tem suas próprias fotos ou um resultado, e o arquivo do leilão deve mostrar as fotos. Revogar a chave que gerou as URLs encerra todas de uma vez.
- Uma nova tentativa retornou 422. Por quê?
- A mesma Idempotency-Key voltou com um corpo diferente. A causa mais comum é uma validade recalculada a partir do relógio no momento da nova tentativa. Defina a validade quando o lote for criado e armazene-a; assim, a nova tentativa envia o mesmo corpo e repete a primeira resposta.
- A renderização pode mostrar a pintura do lote?
- Sim: o parâmetro color aceita qualquer um dos nomes predefinidos ou um hex, para que a imagem corresponda à pintura da ficha de consignação sem custo extra. Ainda é o modelo naquela cor, não o lote em si, então o aviso permanece.
- Quanto custa um leilão de 400 lotes?
- 400 créditos: 1 crédito por lote quando a URL é gerada, e uma geração repetida não cobra nada. Todo carregamento dentro da validade da URL é gratuito. Os planos limitam os veículos distintos por mês (Free 100, Pro 2.500 e Business 15.000; Enterprise não tem limite), e lotes que compartilham marca, modelo e ano contam uma vez só.