Car Image API

Clientes · Classificados e catálogos de compra de carros

Como colocar uma imagem de carro com qualidade de estúdio em todos os anúncios do marketplace

Como sites de classificados e catálogos de compra de carros renderizam uma imagem de destaque por ano-modelo em escala de catálogo: IDs de veículo, enquadramento, ritmo de requisições, cache e o código.

O problema: um anúncio precisa de uma imagem antes que o vendedor envie uma

Um site de classificados ou um catálogo de compra de carros vive ou morre pela sua grade. Cada card precisa de uma imagem no momento em que o anúncio é criado, e a foto do próprio vendedor chega depois, se chegar, num ângulo aleatório, numa garagem qualquer. A fotografia de banco de imagens tem o problema oposto: existe uma foto de imprensa licenciada para os carros populares nos anos populares, no ângulo e no fundo que a montadora escolheu, e para nada mais. Um catálogo que cobre de 1990 até o ano-modelo atual tem dezenas de milhares de lacunas.

O que as equipes que criam esses sites querem é entediante no melhor sentido: uma imagem de estúdio por ano-modelo, o mesmo ângulo e a mesma cor para todas, transparente para se encaixar em qualquer fundo do card, leve o bastante para ser servida dez mil vezes por dia.

Como é uma integração de catálogo

Os catálogos construídos sobre a API hoje têm todos o mesmo formato, seja um site de classificados populando seus anúncios, um app de compra de carros renderizando seus cards de comparação ou um app móvel mostrando uma imagem principal por veículo:

  • Uma carga inicial, depois um fluxo contínuo. Milhares de renderizações em poucas horas para cobrir o catálogo e, depois, algumas por dia, à medida que chegam novos anos-modelo e solicitações.
  • Um ângulo, uma pintura ou uma paleta curta. A maioria escolhe front-3-4 ou side em prata ou branco. Um catálogo de compras que permite ao usuário escolher a cor renderiza algumas cores predefinidas por veículo; cada uma é mais uma imagem de veículo, não um novo pipeline.
  • WebP em uma caixa fixa. De 512 a 1024 pixels de largura, entregue exatamente na caixa que o card usa, para que o front-end nunca precise cortar a imagem nem adicionar faixas.
  • Armazenadas pelo ID do veículo. As renderizações vão para o armazenamento de objetos ou a CDN da própria equipe, ou as páginas carregam URLs assinadas; de qualquer forma, a chave é o ID estável veh_… do catálogo, não uma string de marca-modelo-ano.

O restante desta página é esse pipeline, passo a passo, com o código. Ele pressupõe uma chave de API com o escopo images:read e um plano dimensionado para o catálogo (veja as perguntas frequentes).

Etapa 1: percorra o catálogo por id, nunca pela grafia

A falha mais comum na construção de um catálogo não é a renderização, é a grafia. Uma construção que gera strings de marca e modelo a partir de outro banco de dados pode gastar a maior parte das requisições com 404 vehicle_not_found: gratuito, mas uma ida e volta desperdiçada a cada vez, e uma grade cheia de buracos. A solução é deixar o catálogo dizer o que existe. GET /api/v1/vehicles é gratuito, não exige chave, e cada modelo retornado traz um ID de veículo estável (veh_ mais 13 caracteres) que nunca muda entre deploys ou versões do catálogo. Armazene esse ID no anúncio e renderize por ele.

Percorra um ano-modelo
# every make with a 2024 model year (free, no key)
curl -s "https://carimage.dev/api/v1/vehicles?year=2024"

# every 2024 model of one make, each with the id you will store
curl -s "https://carimage.dev/api/v1/vehicles?year=2024&makeId=584"
200 OK (resumido)
{
  "data": {
    "year": 2024,
    "make_id": 584,
    "models": [
      {
        "name": "718",
        "slug": "718",
        "vehicle_type": "Passenger Car",
        "id": "veh_b4bf090fyb53k"
      },
      {
        "name": "911",
        "slug": "911",
        "vehicle_type": "Passenger Car",
        "id": "veh_78qtwrgh37bkr"
      }
    ]
  }
}

?make=porsche&model=911 lista todos os anos de um modelo com um id por ano, e ?q= faz uma busca por texto livre quando um vendedor digita um nome. O mesmo catálogo é publicado como o pacote aberto @meterapp/vehicle-db, para que menus suspensos e validações possam rodar offline com a mesma lista. De qualquer forma, ao fim desta etapa você tem uma lista de ids, e nenhuma etapa seguinte precisa escrever o nome de um carro de novo.

Anúncios que já nomeiam seus carros com a grafia de outro banco de dados são mapeados em uma só chamada: POST /api/v1/vehicles/check lê até 2.000 deles do mesmo jeito que uma renderização leria e responde a cada um com o id de veículo, os veículos reais mais próximos quando o catálogo não tem esse carro, ou uma ausência a ser ignorada em todas as vistas e cores. car-image check listings.csv --out checked.csv grava os ids de volta no arquivo. É gratuito e evita descobrir as lacunas um 404 por vez.

Etapa 2: um único formato de requisição para todos os anúncios

Defina o card uma vez e codifique-o na query string. Tudo o que vem depois de vehicle= abaixo é uma decisão de enquadramento que você toma uma única vez para todo o catálogo:

Uma imagem de anúncio
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://carimage.dev/api/v1/images/car?vehicle=veh_78qtwrgh37bkr&view=front-3-4&color=silver&w=800&h=600&fit=contain&trim=1&padding=8&format=webp" \
  -o veh_78qtwrgh37bkr.webp
  • w=800&h=600&fit=contain entrega exatamente 800×600 com o carro inteiro visível e preenchimento transparente ao redor, para que todo card tenha a mesma tela. Já fit=cover preenche a caixa e recorta pelo centro.
  • trim=1&padding=8 recorta no contorno do carro antes de ajustar, com 8% do lado maior como margem, para que um Suburban e um Miata preencham a caixa da mesma forma, em vez de flutuarem no quadro original.
  • color=silver é o padrão e o neutro mais seguro; qualquer uma das 15 predefinições ou qualquer hex conta da mesma forma: uma imagem de veículo.
  • format=webp é a menor codificação com canal alfa sem perdas. Quando um navegador carrega a URL diretamente (URLs assinadas, passo 4), format=auto negocia WebP ou PNG para cada cliente.

A resposta traz ETag, X-Image-Source (cache ou generated) e X-Vehicle-Id, o veículo do catálogo que foi entregue. Guarde o ETag junto com o arquivo: uma requisição posterior com If-None-Match responde 304 e não custa nada, e é assim que uma ressincronização noturna continua gratuita.

Passo 3: dose o preenchimento retroativo dentro dos limites

Três limites determinam uma carga inicial, e cada um deles se anuncia nos cabeçalhos em vez de falhar silenciosamente:

  • Requisições por minuto. 120 por chave nos planos Free e Pro, 600 no Business, 1.200 no Enterprise, além de um limite por conta somando todas as chaves. Toda resposta autenticada traz RateLimit-Remaining; um 429 traz Retry-After. De quatro a oito requisições simultâneas é mais do que suficiente.
  • Novas vistas de veículo. A primeira requisição de uma vista de veículo que ainda não preparamos leva um pouco de tempo enquanto a preparamos para que possa ser exibida em qualquer cor; todas as requisições seguintes, em qualquer cor, são servidas do cache. Preparar novas vistas consome a parcela da conta no orçamento diário de renderização (2% no Free, 10% no Pro, 25% no Business, 50% no Enterprise). Passado esse limite, um novo render responde 429 com code: account_generation_cap e reset_at (meia-noite UTC), nada é cobrado e as imagens em cache continuam sendo servidas. Uma grande carga retroativa leva algumas noites, não uma só.
  • Imagens de veículo por mês. O Free inclui 100, o Pro 2.500 e o Business 10.000; o Enterprise é negociado. Acima dessa franquia, uma solicitação de imagem ainda não contabilizada neste mês responde 402 (plan_image_limit) e não conta nada; uma imagem já contabilizada neste mês é sempre servida, então uma grade em produção nunca quebra.

Falhas de renderização (502) devolvem sua imagem de veículo automaticamente, requisições simultâneas para a mesma variante nova compartilham uma única renderização, e um GET simples pode ser repetido com segurança. O SDK repete 429 e 503 por você, respeitando o Retry-After, e nunca repete um 402.

Um backfill retomável com o SDK TypeScript
import { CarImageClient, CarImageError } from "@meterapp/car-image-sdk";

const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });

// One make, one model year: the ids are what you store and what you render by.
const { data } = await client.vehicles({ year: 2024, makeId: 584 });
const queue = data.models.flatMap((model) => (model.id ? [model.id] : []));

// Four in flight stays well inside 120 requests a minute, and a cold render
// that takes a few seconds never holds up the rest of the batch.
await Promise.all(
  Array.from({ length: 4 }, async () => {
    for (let id = queue.shift(); id; id = queue.shift()) {
      try {
        const image = await client.getImage({
          vehicle: id, view: "front-3-4", color: "silver",
          width: 800, height: 600, fit: "contain", trim: true, padding: 8, format: "webp",
        });
        await store(id, image.bytes, image.etag); // your object store or CDN, keyed by vehicle id
      } catch (error) {
        // 429 and 503 were already retried with Retry-After; a 404 is a vehicle to request, not a failure.
        if (error instanceof CarImageError && error.status === 404) continue;
        throw error;
      }
    }
  })
);

Passo 4: sirva a imagem pela sua CDN ou por URLs assinadas

Há duas boas maneiras de colocar a imagem diante do comprador, e as implementações de catálogo usam as duas:

  • Armazene os bytes. Grave cada renderização no seu próprio armazenamento de objetos ou CDN com o ID do veículo e sirva-a como qualquer outro arquivo estático. Essa é a resposta certa para uma grade exibida milhões de vezes: nenhuma chamada para nós a cada carregamento, e a licença de um plano pago cobre as cópias que você mantém para servir o seu produto.
  • Gere URLs assinadas. POST /api/v1/image-urls aceita até cinquenta imagens por chamada, conta a imagem de cada URL como uma imagem de veículo do mês quando ela é gerada e retorna URLs sem chave que o navegador carrega diretamente. Defina ttl_seconds em até sete dias; adicione renew: true para páginas de anúncios que ficam no ar por mais tempo que isso (uma renovação conta a imagem uma vez no mês em que ocorre, e nada mais se ela já tiver sido contada naquele mês). Restrinja a chave às origens do seu site no painel, e uma URL copiada para outro lugar responde 403.

Envie um cabeçalho Idempotency-Key por página de anúncios ao gerar em lotes: uma chamada repetida com a mesma chave e o mesmo corpo reproduz a primeira resposta e não conta nada de novo.

listings.json — cinquenta por vez, enquadrados como na etapa 2
{
  "images": [
    {
      "vehicle": "veh_78qtwrgh37bkr",
      "view": "front-3-4",
      "color": "silver",
      "width": 800,
      "height": 600,
      "fit": "contain",
      "trim": true,
      "padding": 8,
      "format": "auto"
    },
    {
      "vehicle": "veh_b4bf090fyb53k",
      "view": "front-3-4",
      "color": "silver",
      "width": 800,
      "height": 600,
      "fit": "contain",
      "trim": true,
      "padding": 8,
      "format": "auto"
    }
  ]
}

Quando um veículo não está no catálogo

Pesquise primeiro: GET /api/v1/vehicles?q= encontra um modelo por nome, prefixo ou correspondência aproximada, e POST /api/v1/images/resolve transforma uma frase em texto livre em parâmetros exatos, com um nível de confiança que orienta a decisão. O que ainda faltar depois disso vai para o quadro de solicitações: POST /api/v1/requests cria a solicitação ou, quando já existe uma aberta para a mesma marca, modelo e ano, registra um voto nela em vez de duplicá-la. O próprio catálogo é open source, e as adições chegam a todos os clientes.

CLI, SDK e MCP

Todo o pipeline roda pela CLI, sem nenhum código, e foi assim que mais de um catálogo na API foi populado:

npx @meterapp/car-image
# One file per vehicle id. Re-running skips what exists, so a stopped backfill resumes.
while read -r id; do
  [ -f "cdn/$id.webp" ] || npx @meterapp/car-image get --vehicle "$id" \
      --view front-3-4 --color silver --width 800 --height 600 --fit contain \
      --trim --padding 8 --format webp --out "cdn/$id.webp"
done < vehicle-ids.txt

# Or fifty signed URLs in one call, for a page of listings the browser loads itself.
npx @meterapp/car-image url --batch listings.json --ttl 604800 --renew --json
  • SDK: client.vehicles({ year, makeId }) para listar, client.getImage({ vehicle, … }) para renderizar, client.createImageUrls(…) para lotes de URLs assinadas. Consulte a documentação de imagens para ver todos os parâmetros.
  • MCP: um assistente conectado a https://carimage.dev/api/mcp tem o mesmo catálogo (search_vehicles, resolve_vehicle) e as mesmas renderizações (get_car_image, create_car_image_urls), o que é a ferramenta certa para um curador que adiciona um veículo manualmente, e a errada para um preenchimento retroativo de milhares: use a CLI ou o SDK para isso e deixe o assistente para os casos restantes. A configuração está na página do MCP.

Perguntas frequentes

Qual ângulo um anúncio deve usar?
front-3-4 é o padrão e o ângulo principal clássico, com a frente voltada para a esquerda. Cada ângulo do lado esquerdo do carro tem um gêmeo -right (front-3-4-right, side-right, rear-3-4-right), para que um card fique voltado para dentro do layout, e não para fora. Escolha um e mantenha-o: a consistência entre os anúncios é a maior parte do valor.
Quanto custa um catálogo de 20.000 anúncios?
20.000 imagens de veículo por mês se cada anúncio mostrar um veículo em uma cor e uma vista, cada uma entregue quantas vezes for preciso, em qualquer tamanho; anúncios que compartilham veículo, cor e vista são uma única imagem, e revalidar com If-None-Match é gratuito. O Pro inclui 2.500 imagens de veículo por mês e o Business, 10.000, então um catálogo desse tamanho é Enterprise, ou um Image Pack de pagamento único que você mantém e hospeda por conta própria para sempre. Um backfill além da cota responde 402 e não conta nada.
Podemos manter os arquivos na nossa própria CDN?
Sim. Um plano pago licencia as renderizações para uso dentro do seu produto, incluindo cópias nos seus próprios servidores ou CDN, sem atribuição, enquanto o plano estiver ativo; em até 30 dias após o término, as cópias armazenadas são excluídas (as imagens de um Image Pack podem ser mantidas para sempre). O que a licença não permite é oferecê-las como biblioteca, feed ou conjunto de dados independente. É um plano ativo que renderiza o próximo ano-modelo.
O que acontece quando o catálogo recebe um novo ano-modelo?
O catálogo @meterapp/vehicle-db é atualizado à medida que as fontes publicam, e os ids existentes nunca mudam. Liste o novo ano, renderize os ids que você ainda não tem e deixe o resto como está. As requisições trazem um ETag; uma requisição repetida com If-None-Match responde 304 e não custa nada.
Por que algumas das nossas strings de marca e modelo retornam 404?
Porque eles foram escritos por outro banco de dados. O catálogo aceita nomes, slugs e muitas variantes de emblema e de carroceria, mas uma string que ele nunca viu resulta em um 404 vehicle_not_found (gratuito, mas uma ida e volta desperdiçada). Enumere o catálogo e armazene os IDs dele em vez de traduzir suas próprias strings, e use a busca ou a resolução para os casos restantes.