Car Image API

Casos de uso · Leilões

Imagens de carros para anúncios de leilão

Liste lotes de leilão antes de as fotos chegarem, cada um com uma imagem de estúdio do seu ano-modelo identificada como renderização de catálogo, por meio de URLs assinadas que expiram com o leilão.

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.

Decodificar no cadastro do lote
# 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.

Ano, depois marca, depois 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"
200 OK (resumido)
{
  "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.

A imagem do lote até as fotos chegarem
<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: true e 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 em 409 com um Retry-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.

URL do lote 117
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 charged

Passo 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_seconds no máximo e renew_days cobrindo 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 em renews_until.
  • Mantenha max_uses em 0, o padrão: uma URL ilimitada pode ser armazenada em cache publicamente durante toda a sua validade, enquanto uma limitada é private, no-store e 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.
Entrada de lotes com o SDK de TypeScript
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 arrive

Planeje 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ó.

Continue lendo

História de cliente · Marketplaces automotivosComo colocar uma imagem de carro com qualidade de estúdio em todos os anúncios do marketplaceUm anúncio precisa de uma imagem antes que o vendedor envie uma. Os marketplaces preenchem um catálogo inteiro com um ângulo e uma cor de pintura e depois mantêm o mesmo formato de solicitação para cada novo ano-modelo.