Car Image API

お客様事例 · クラシファイドサイト・車探しカタログ

すべてのマーケットプレイス掲載にスタジオ品質の車両画像を載せる方法

クラシファイドサイトや車探しカタログが、カタログ規模でモデルイヤーごとに1枚のメイン画像をレンダリングする方法:車両ID、フレーミング、ペース配分、キャッシュ、そしてコード。

課題:出品者が写真をアップロードする前から、掲載には画像が必要

クラシファイドサイトや自動車ショッピングカタログの成否は、一覧グリッドで決まります。掲載が作成された瞬間からすべてのカードに画像が必要ですが、出品者自身の写真は届くとしても後になってからで、アングルも撮影場所もまちまちです。ストックフォトには逆の問題があります。ライセンス付きのプレス写真があるのは人気年式の人気車種だけで、アングルや背景もメーカーが選んだものに限られ、それ以外は存在しません。1990 年から現行モデルイヤーまでをカバーするカタログには、何万もの空白が生じます。

こうしたサイトを構築するチームが求めているのは、良い意味で地味なものです。モデルイヤーごとに 1 枚のスタジオ画像、すべて同じアングルと同じ塗装色、カードの背景が何であってもなじむ透過背景、そして 1 日 1 万回配信できるほど軽いファイルサイズです。

カタログ連携の構成例

現在このAPI上で動いているカタログ構築は、掲載車両を初期投入する売買掲載サイトでも、比較カードを表示する車探しアプリでも、車両ごとにメイン画像を1枚表示するモバイルアプリでも、すべて同じ構成になっています。

  • 最初にバックフィル、その後は少量ずつ。数時間で数千件のレンダーを行ってカタログ全体をカバーし、その後は新しいモデルイヤーやリクエストに応じて1日に数件ずつ追加します。
  • 1つのアングル、1色、または少数のカラーパレット。多くの場合、front-3-4またはsideをシルバーかホワイトで選びます。ユーザーが色を選べるショッピングカタログでは、車両ごとにいくつかのプリセットをレンダリングします。それぞれ車両画像が1枚増えるだけで、新しいパイプラインは必要ありません。
  • 固定サイズのWebP。幅512〜1024ピクセルで、カードが使うボックスにぴったり合わせて配信するため、フロントエンドで切り抜きやレターボックスが発生しません。
  • 車両 ID で保存。レンダリング画像はチーム自身のオブジェクトストレージや CDN に保存されるか、ページが署名付き URL を読み込みます。いずれの場合も、キーはメーカー・モデル・年式の文字列ではなく、カタログの安定した veh_… ID です。

このページの残りでは、そのパイプラインをコード付きで順を追って説明します。images:readスコープを持つAPIキーと、カタログの規模に合ったプランを前提としています(FAQを参照)。

ステップ1:カタログは綴りではなく、idで列挙する

カタログ構築で最もよくある失敗はレンダリングではなく、スペルです。別のデータベースからメーカー名やモデル名の文字列を生成する構築では、リクエストの大半が 404 vehicle_not_found に費やされることがあります。料金はかかりませんが、毎回往復が無駄になり、グリッドには穴が空きます。解決策は、何が存在するかをカタログに教えてもらうことです。GET /api/v1/vehicles は無料でキーも不要です。返されるすべてのモデルには、デプロイやカタログのリリースをまたいでも変わらない安定した車両 ID(veh_ と 13 文字)が付いています。その ID を掲載情報に保存し、それを使ってレンダリングしてください。

モデルイヤー単位で一覧を取得する
# 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(一部省略)
{
  "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 は1つのモデルの全年式を年式ごとのidとともに一覧表示し、?q= は出品者が車名を入力したときにフリーテキストで検索します。同じカタログはオープンな @meterapp/vehicle-db パッケージとしても公開されているため、ドロップダウンや入力チェックをオフラインで同一のリストに対して実行できます。どちらの方法でも、このステップを終えた時点でidのリストが手元にあり、以降の処理で車名を綴る必要は一切なくなります。

すでに別のデータベースの表記で車名が記載されているリスティングは、1回の呼び出しでマッピングできます。POST /api/v1/vehicles/checkは最大2,000件をレンダリング時と同じ方法で読み取り、それぞれに車両ID、カタログに該当車両がない場合は最も近い実在の車両、またはすべてのビューと色でスキップすべき不一致のいずれかを返します。car-image check listings.csv --out checked.csvを使うと、IDがファイルに書き戻されます。無料で利用でき、欠落を404で1件ずつ見つける手間がなくなります。

ステップ2:すべての掲載に共通する1つのリクエスト形式

カードの仕様は一度決めて、クエリ文字列にエンコードしてください。以下のvehicle=以降はすべて、カタログ全体に対して一度だけ決めるフレーミングの設定です。

掲載用画像 1 枚
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 は車全体が見える状態で周囲に透明な余白を付け、ちょうど 800×600 で出力するため、すべてのカードが同じキャンバスになります。fit=cover は代わりに枠全体を埋め、中央でトリミングします。
  • trim=1&padding=8を指定すると、サイズを合わせる前に車の輪郭に沿って切り抜き、長辺の8%を余白として確保します。そのため、SuburbanでもMiataでも、元のフレーム内に浮いたように見えることなく、同じようにボックスを埋めます。
  • color=silverがデフォルトで、最も無難なニュートラルカラーです。15種類のプリセットや任意のhexを指定しても、同じく車両画像1枚としてカウントされます。
  • format=webpは、アルファ付きロスレスで最も小さいエンコード形式です。ブラウザがURLを直接読み込む場合(署名付きURL、ステップ4)、format=autoがクライアントごとにWebPかPNGを自動選択します。

レスポンスには、ETag、X-Image-Source(cacheまたはgenerated)、そして配信されたカタログ車両を示すX-Vehicle-Idが含まれます。ETagはファイルと一緒に保存してください。後でIf-None-Matchを付けてリクエストすると304が返り、料金はかかりません。これにより、毎晩の再同期を無料で行えます。

ステップ3:制限内でバックフィルのペースを調整する

バックフィルには3つの制限があり、いずれも暗黙に失敗するのではなく、ヘッダーで通知されます。

  • 1分あたりのリクエスト数。FreeとProではキーごとに120、Businessでは600、Enterpriseでは1,200で、さらにすべてのキーを合算したアカウント単位の上限があります。認証済みのすべてのレスポンスにはRateLimit-Remainingが含まれ、429にはRetry-Afterが含まれます。同時実行リクエストは4~8件で十分です。
  • 新しい車両ビュー。 まだ準備していない車両ビューへの最初のリクエストは、どの色でも表示できるよう準備するため少し時間がかかります。以降のリクエストは、どの色でもキャッシュから配信されます。新しいビューの準備には、1日のレンダリング予算のうちアカウントへの割り当て分が使われます(Freeは2%、Proは10%、Businessは25%、Enterpriseは50%)。上限を超えると、新しいレンダリングはcode: account_generation_capとreset_at(UTCの午前0時)を含む429を返します。料金は発生せず、キャッシュ済みの画像は引き続き配信されます。大規模なバックフィルは1晩ではなく、数晩かけて行うことになります。
  • 1か月あたりの車両画像数。Free は 100 枚、Pro は 2,500 枚、Business は 10,000 枚が含まれ、Enterprise は個別交渉です。上限を超えると、今月まだカウントされていない画像へのリクエストには 402(plan_image_limit)が返り、何もカウントされません。今月すでにカウントされた画像は常に配信されるため、公開中のグリッドが崩れることはありません。

レンダリングに失敗した場合(502)、その車両画像のカウントは自動的に戻されます。同じ新しいバリエーションへの同時リクエストは1回のレンダリングを共有し、通常のGETは安全に再試行できます。SDKはRetry-Afterに従って429と503を自動で再試行し、402は決して再試行しません。

TypeScript SDKによる再開可能なバックフィル
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;
      }
    }
  })
);

ステップ4:自社のCDNまたは署名付きURLで配信する

購入希望者に画像を届ける良い方法は2つあり、カタログ構築ではその両方を使います:

  • バイナリを保存する。各レンダリングを車両 id ごとに自社のオブジェクトストレージや CDN に保存し、他のアセットと同様に配信します。数百万回表示されるグリッドにはこれが最適です。表示のたびに当社 API を呼び出す必要がなく、有料プランのライセンスは、自社製品の提供のために保持するコピーにも適用されます。
  • 署名付きURLを発行します。POST /api/v1/image-urlsは1回の呼び出しで最大50枚の画像を受け付け、発行時に各URLの画像を今月の車両画像として計上し、ブラウザが直接読み込めるキー不要のURLを返します。ttl_secondsは最大7日まで設定できます。それより長く掲載される一覧ページにはrenew: trueを追加してください(更新時は、更新が発生した月に画像が1回計上され、その月にすでに計上済みであれば追加の計上はありません)。ダッシュボードでキーを自社サイトのオリジンに制限すると、他の場所にコピーされたURLは403を返します。

リスティング用の URL をバッチで発行する場合は、ページごとに Idempotency-Key を送信してください。同じキーと本文でリトライされた呼び出しは最初のレスポンスを再送し、再度カウントされることはありません。

listings.json — 50 件ずつ、ステップ 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"
    }
  ]
}

車両がカタログにない場合

まずは検索してください。GET /api/v1/vehicles?q= はモデルを名前、前方一致、あいまい一致で検索し、POST /api/v1/images/resolve は自由入力のフレーズを、判断に使える信頼度付きの正確なパラメーターに変換します。それでも見つからない車両はリクエストボードへ。POST /api/v1/requests はリクエストを作成し、同じメーカー・モデル・年式の未対応リクエストがすでにある場合は、重複させずにそのリクエストに賛成票を投じます。カタログ自体はオープンソースで、追加分はすべてのお客様に提供されます。

CLI、SDK、MCP

パイプライン全体はコードを一切書かずに CLI から実行できます。API 上の複数のカタログも、この方法で初期データが投入されました:

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 })、レンダリングには client.getImage({ vehicle, … })、署名付きURLの一括作成には client.createImageUrls(…) を使います。すべてのパラメーターについては画像のドキュメントをご覧ください。
  • MCP: https://carimage.dev/api/mcp に接続したアシスタントは、同じカタログ(search_vehicles、resolve_vehicle)と同じレンダー(get_car_image、create_car_image_urls)を利用できます。担当者が車両を手作業で追加する場合には最適ですが、数千件のバックフィルには向きません。その場合は CLI か SDK を使い、アシスタントは取りこぼし分の対応に使ってください。セットアップ方法は MCP ページをご覧ください。

よくある質問

出品にはどのアングルを使うべきですか?
front-3-4 はデフォルトであり、車首を左に向けた定番のヒーローアングルです。車の左側を写すすべてのアングルには -right の対となるアングル(front-3-4-right、side-right、rear-3-4-right)があるため、カードをレイアウトの外側ではなく内側に向けられます。1 つを選んで統一してください。出品間の一貫性こそが価値の大部分です。
20,000 件の出品があるカタログでは、費用はいくらですか?
各掲載で1台の車両を1つの塗装色・1つのビューで表示する場合、月20,000枚の車両画像となり、各画像はどのサイズでも必要なだけ配信できます。車両・塗装色・ビューが同じ掲載同士は1枚の画像としてカウントされ、If-None-Matchによる再確認は無料です。Proには月2,500枚、Businessには月10,000枚の車両画像が含まれるため、その規模のカタログにはEnterprise、または買い切りで永続的に保持・セルフホストできるImage Packが適しています。利用枠を超えるバックフィルには402が返され、カウントはされません。
自社の CDN にファイルを保存できますか?
はい。有料プランでは、プランが有効な間、自社製品内でレンダリング画像を使用するライセンスが付与されます。自社サーバーやCDN上のコピーも対象で、クレジット表記は不要です。プラン終了後30日以内に、保存したコピーは削除されます(Image Packの画像は永続的に保持できます)。ライセンスで認められていないのは、画像を単体のライブラリ、フィード、データセットとして提供することです。次のモデルイヤーの車両をレンダリングするには、有効なプランが必要です。
カタログに新しいモデルイヤーが追加されるとどうなりますか?
@meterapp/vehicle-db カタログはソースの公開に合わせて更新され、既存の id は変わりません。新しい年式を列挙し、まだお持ちでない id をレンダリングし、それ以外はそのままにしてください。リクエストには ETag が付いており、If-None-Match を付けた再リクエストは 304 を返し、費用はかかりません。
一部のメーカー名とモデル名の文字列で404が返されるのはなぜですか?
別のデータベースの表記で書かれているためです。カタログは名前、スラッグ、多くのバッジ表記やボディスタイルの表記ゆれを受け付けますが、一度も登録されていない文字列は 404 vehicle_not_found になります(無料ですが、通信が無駄になります)。自前の文字列を変換するのではなく、カタログを列挙してその id を保存し、残ったものは検索または resolve で解決してください。