Car Image API

Khách hàng · Trang rao vặt & danh mục mua sắm ô tô

Cách đưa ảnh xe chuẩn studio lên mọi tin đăng trên sàn giao dịch

Cách các trang rao vặt và danh mục mua sắm ô tô render một ảnh chính cho mỗi đời xe ở quy mô danh mục: id xe, bố cục khung hình, điều tiết tốc độ, bộ nhớ đệm và mã nguồn.

Vấn đề: tin đăng cần có ảnh trước khi người bán tải ảnh lên

Một trang rao vặt hay một danh mục mua sắm ô tô sống còn nhờ lưới hiển thị của mình. Mỗi thẻ cần có ảnh ngay khi tin đăng xuất hiện, còn ảnh của người bán thì đến sau, nếu có, ở một góc chụp ngẫu nhiên trên một lối đi ngẫu nhiên nào đó. Ảnh stock lại gặp vấn đề ngược lại: ảnh báo chí có bản quyền chỉ có cho những mẫu xe phổ biến ở những năm phổ biến, với góc chụp và phông nền do nhà sản xuất chọn, ngoài ra thì không có gì. Một danh mục bao phủ từ 1990 đến đời xe hiện tại có tới hàng chục nghìn chỗ trống.

Điều mà các nhóm xây dựng những trang này mong muốn thật nhàm chán theo nghĩa tốt nhất: mỗi đời xe một ảnh studio, cùng một góc chụp và cùng một màu sơn cho tất cả, nền trong suốt để đặt lên bất kỳ nền thẻ nào, và đủ nhẹ để phục vụ mười nghìn lần mỗi ngày.

Một tích hợp danh mục trông như thế nào

Các hệ thống xây dựng danh mục đang chạy trên API hiện nay đều có cùng một cấu trúc, dù đó là một trang rao vặt nạp sẵn tin đăng, một ứng dụng mua xe hiển thị thẻ so sánh, hay một ứng dụng di động hiển thị một ảnh chính cho mỗi xe:

  • Một đợt backfill, rồi bổ sung nhỏ giọt. Hàng nghìn bản render trong vài giờ để phủ toàn bộ danh mục, sau đó vài bản mỗi ngày khi có năm model mới và yêu cầu mới.
  • Một góc chụp, một màu sơn, hoặc một bảng màu ngắn. Phần lớn chọn front-3-4 hoặc side với màu bạc hoặc trắng. Danh mục mua sắm cho phép người dùng chọn màu sẽ tạo vài màu có sẵn cho mỗi xe; mỗi màu là thêm một hình ảnh xe, không phải một quy trình mới.
  • WebP trong khung cố định. Rộng từ 512 đến 1024 pixel, được trả về đúng kích thước khung mà thẻ sử dụng, nên front end không bao giờ phải cắt ảnh hay thêm viền.
  • Lưu theo id xe. Ảnh render được lưu vào kho đối tượng hoặc CDN riêng của đội ngũ, hoặc các trang tải URL đã ký; dù theo cách nào, khóa vẫn là id veh_… ổn định của danh mục, không phải chuỗi hãng-mẫu-năm.

Phần còn lại của trang này trình bày quy trình đó, từng bước một, kèm mã nguồn. Trang giả định bạn có API key với phạm vi images:read và một gói có quy mô phù hợp với danh mục xe (xem phần Câu hỏi thường gặp).

Bước 1: liệt kê danh mục theo id, không bao giờ theo cách viết tên

Lỗi phổ biến nhất khi xây dựng danh mục không nằm ở khâu render mà ở chính tả. Một bản build tạo chuỗi hãng và mẫu xe từ một cơ sở dữ liệu khác có thể tiêu phần lớn yêu cầu vào 404 vehicle_not_found: miễn phí, nhưng mỗi lần là một lượt gọi đi về vô ích, và một lưới ảnh đầy chỗ trống. Cách khắc phục là để chính danh mục cho biết những gì đang có. GET /api/v1/vehicles miễn phí, không cần khóa, và mỗi mẫu xe được trả về đều có một id xe ổn định (veh_ cộng 13 ký tự), không bao giờ thay đổi qua các lần triển khai hay phát hành danh mục. Hãy lưu id đó vào tin đăng và render theo id đó.

Liệt kê theo một năm đời xe
# 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 (đã rút gọn)
{
  "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 liệt kê mọi năm của một mẫu xe, mỗi năm có một id, và ?q= tìm kiếm văn bản tự do khi người bán gõ tên xe. Cùng danh mục này được phát hành dưới dạng gói mã nguồn mở @meterapp/vehicle-db, nên danh sách thả xuống và việc xác thực có thể chạy ngoại tuyến trên đúng danh sách đó. Dù theo cách nào, đến cuối bước này bạn sẽ có một danh sách id, và không khâu nào phía sau phải viết tên xe nữa.

Các tin đăng đã gọi tên xe theo cách viết của một cơ sở dữ liệu khác được ánh xạ chỉ trong một lần gọi: POST /api/v1/vehicles/check đọc tối đa 2.000 tin theo đúng cách một lần render sẽ đọc, và trả lời từng tin bằng vehicle id của nó, các xe thực tế gần nhất khi danh mục không có chiếc xe đó, hoặc một kết quả không khớp để bỏ qua ở mọi góc nhìn và màu sắc. car-image check listings.csv --out checked.csv ghi các id trở lại vào tệp. Tính năng này miễn phí, và thay cho việc phát hiện từng chỗ thiếu qua mỗi lỗi 404.

Bước 2: một cấu trúc yêu cầu cho mọi tin đăng

Quyết định thiết kế thẻ một lần và mã hóa nó vào query string. Mọi thứ sau vehicle= bên dưới là một quyết định về bố cục khung hình mà bạn chỉ đưa ra một lần cho toàn bộ danh mục:

Một ảnh tin đăng
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 trả về đúng kích thước 800×600 với toàn bộ chiếc xe hiển thị và phần đệm trong suốt xung quanh, nhờ đó mọi thẻ đều có cùng khung. Còn fit=cover thì lấp đầy khung và cắt từ giữa.
  • trim=1&padding=8 cắt sát theo đường viền của xe trước khi căn khung, với lề bằng 8% cạnh dài hơn, nhờ vậy một chiếc Suburban và một chiếc Miata lấp đầy khung theo cùng một cách thay vì lơ lửng trong khung hình gốc.
  • color=silver là mặc định và là màu trung tính an toàn nhất; bất kỳ màu nào trong 15 màu có sẵn hoặc mã hex nào cũng được tính như nhau, một hình ảnh xe.
  • format=webp là định dạng mã hóa có kênh alpha không mất dữ liệu nhỏ nhất. Khi trình duyệt tải URL trực tiếp (URL có chữ ký, bước 4), format=auto sẽ tự chọn WebP hoặc PNG cho từng client.

Phản hồi chứa ETag, X-Image-Source (cache hoặc generated) và X-Vehicle-Id, tức xe trong danh mục đã được trả về. Hãy lưu ETag cùng với tệp: một yêu cầu sau đó có If-None-Match sẽ trả về 304 và không tốn phí, nhờ vậy việc đồng bộ lại hằng đêm vẫn miễn phí.

Bước 3: điều tiết tốc độ tạo bù ảnh cho dữ liệu hiện có trong giới hạn cho phép

Ba giới hạn định hình một đợt backfill, và mỗi giới hạn đều tự báo qua header thay vì thất bại âm thầm:

  • Số yêu cầu mỗi phút. 120 mỗi key với gói Free và Pro, 600 với Business, 1.200 với Enterprise, cộng thêm giới hạn theo tài khoản trên tất cả các key. Mọi phản hồi đã xác thực đều có RateLimit-Remaining; phản hồi 429 có Retry-After. Bốn đến tám yêu cầu đồng thời là quá đủ.
  • Góc nhìn xe mới. Yêu cầu đầu tiên cho một góc nhìn xe mà chúng tôi chưa chuẩn bị sẽ mất một chút thời gian để chuẩn bị, nhờ đó góc nhìn có thể hiển thị ở bất kỳ màu nào; mọi yêu cầu sau đó, ở bất kỳ màu nào, đều được phục vụ từ bộ nhớ đệm. Việc chuẩn bị góc nhìn mới sử dụng phần ngân sách render hằng ngày dành cho tài khoản (2% với Free, 10% với Pro, 25% với Business, 50% với Enterprise). Khi vượt mức này, yêu cầu render mới trả về 429 kèm code: account_generation_cap và reset_at (nửa đêm UTC), không có khoản phí nào bị tính, và các hình ảnh đã lưu đệm vẫn tiếp tục được phục vụ. Một đợt bổ sung dữ liệu lớn sẽ mất vài buổi tối, không phải chỉ một.
  • Số ảnh xe mỗi tháng. Gói Free bao gồm 100, Pro 2.500, Business 10.000; Enterprise được thỏa thuận riêng. Khi vượt quá hạn mức đó, yêu cầu cho một ảnh chưa được tính trong tháng này sẽ trả về 402 (plan_image_limit) và không bị tính gì; ảnh đã được tính trong tháng này luôn được phục vụ, nên lưới ảnh đang hiển thị không bao giờ bị hỏng.

Lỗi render (502) tự động hoàn lại hình ảnh xe, các yêu cầu đồng thời cho cùng một biến thể mới dùng chung một lần render, và một yêu cầu GET thông thường có thể thử lại an toàn. SDK tự động thử lại 429 và 503 cho bạn, tuân theo Retry-After, và không bao giờ thử lại 402.

Nạp bù dữ liệu có thể tiếp tục từ chỗ dừng bằng 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;
      }
    }
  })
);

Bước 4: phân phối ảnh từ CDN riêng hoặc qua signed URL

Có hai cách hiệu quả để đưa hình ảnh đến người mua, và các dự án xây dựng danh mục sử dụng cả hai:

  • Lưu trữ tệp ảnh. Ghi từng ảnh render vào kho đối tượng hoặc CDN của riêng bạn theo id xe và phân phối như mọi tài nguyên khác. Đây là cách phù hợp cho một lưới được hiển thị hàng triệu lần: không cần gọi đến chúng tôi mỗi lần tải, và giấy phép của gói trả phí bao gồm các bản sao bạn lưu giữ để phục vụ sản phẩm của mình.
  • Tạo URL có chữ ký. POST /api/v1/image-urls nhận tối đa năm mươi hình ảnh mỗi lệnh gọi, tính hình ảnh của mỗi URL là một hình ảnh xe của tháng khi URL được tạo, và trả về các URL không chứa khóa để trình duyệt tải trực tiếp. Đặt ttl_seconds tối đa bảy ngày; thêm renew: true cho các trang tin đăng tồn tại lâu hơn thế (mỗi lần gia hạn tính hình ảnh một lần trong tháng diễn ra gia hạn, và không tính thêm nếu hình đã được tính trong tháng đó). Giới hạn khóa cho các origin của trang web bạn trên bảng điều khiển, khi đó URL bị sao chép sang nơi khác sẽ trả về 403.

Gửi một Idempotency-Key cho mỗi trang tin đăng khi bạn tạo URL theo lô: lệnh gọi thử lại với cùng khóa và nội dung sẽ phát lại phản hồi đầu tiên và không tính thêm gì.

listings.json — năm mươi tin mỗi lần, khung hình như bước 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"
    }
  ]
}

Khi xe không có trong danh mục

Hãy tìm kiếm trước: GET /api/v1/vehicles?q= tìm mẫu xe theo tên, tiền tố hoặc khớp gần đúng, và POST /api/v1/images/resolve chuyển một cụm từ tự do thành tham số chính xác kèm độ tin cậy để bạn dựa vào đó xử lý. Những gì vẫn còn thiếu sau đó thuộc về bảng yêu cầu: POST /api/v1/requests tạo yêu cầu hoặc, khi đã có một yêu cầu đang mở cho cùng hãng, mẫu xe và năm, sẽ bình chọn cho yêu cầu đó thay vì tạo bản trùng lặp. Bản thân danh mục là mã nguồn mở, và các bổ sung được cung cấp cho mọi khách hàng.

CLI, SDK và MCP

Toàn bộ quy trình chạy từ CLI mà không cần viết dòng mã nào, đây cũng là cách nhiều danh mục trên API đã được khởi tạo dữ liệu:

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 }) để liệt kê, client.getImage({ vehicle, … }) để kết xuất, client.createImageUrls(…) để tạo hàng loạt URL có chữ ký. Xem tài liệu hình ảnh để biết mọi tham số.
  • MCP: một trợ lý kết nối với https://carimage.dev/api/mcp có cùng danh mục (search_vehicles, resolve_vehicle) và cùng các ảnh render (get_car_image, create_car_image_urls), là công cụ phù hợp khi người biên tập thêm xe thủ công, nhưng không phù hợp để bổ sung hàng nghìn xe: hãy dùng CLI hoặc SDK cho việc đó và để trợ lý xử lý những trường hợp còn sót. Hướng dẫn thiết lập có trên trang MCP.

Câu hỏi thường gặp

Tin đăng nên dùng góc chụp nào?
front-3-4 là góc mặc định và là góc chụp chủ đạo kinh điển, đầu xe hướng sang trái. Mỗi góc phía bên trái của xe đều có bản đối xứng -right (front-3-4-right, side-right, rear-3-4-right), để thẻ có thể hướng vào trong bố cục thay vì ra ngoài. Hãy chọn một góc và giữ nguyên: sự nhất quán giữa các tin đăng tạo nên phần lớn giá trị.
Một danh mục 20.000 tin đăng có chi phí bao nhiêu?
20.000 hình ảnh xe mỗi tháng nếu mỗi tin đăng hiển thị một xe với một màu sơn và một góc nhìn, mỗi hình được phân phối bao nhiêu lần tùy cần ở mọi kích thước; các tin đăng có chung xe, màu sơn và góc nhìn chỉ là một hình ảnh, và kiểm tra lại bằng If-None-Match là miễn phí. Pro bao gồm 2.500 hình ảnh xe mỗi tháng và Business 10.000, nên một danh mục cỡ đó cần gói Enterprise, hoặc một Image Pack mua một lần mà bạn giữ và tự lưu trữ vĩnh viễn. Một đợt bổ sung dữ liệu vượt quá hạn mức sẽ trả về 402 và không bị tính gì cả.
Chúng tôi có thể lưu các tệp trên CDN của riêng mình không?
Có. Gói trả phí cấp phép sử dụng các bản render bên trong sản phẩm của bạn, bao gồm cả bản sao trên máy chủ hoặc CDN của riêng bạn, không cần ghi nguồn, trong thời gian gói còn hiệu lực; trong vòng 30 ngày sau khi gói kết thúc, các bản sao đã lưu sẽ bị xóa (ảnh của Image Pack có thể được giữ vĩnh viễn). Điều giấy phép không cho phép là cung cấp chúng dưới dạng thư viện, nguồn cấp dữ liệu hoặc bộ dữ liệu độc lập. Gói đang hoạt động là thứ giúp render mẫu xe của năm tiếp theo.
Điều gì xảy ra khi danh mục có thêm năm đời xe mới?
Danh mục @meterapp/vehicle-db được cập nhật mỗi khi các nguồn công bố dữ liệu, và các id hiện có không bao giờ thay đổi. Hãy liệt kê năm mới, render những id bạn chưa có và giữ nguyên phần còn lại. Các yêu cầu có kèm ETag; yêu cầu lặp lại với If-None-Match sẽ trả về 304 và không tốn phí.
Vì sao một số chuỗi hãng và mẫu xe của chúng tôi trả về 404?
Vì chúng được viết theo cách của một cơ sở dữ liệu khác. Danh mục chấp nhận tên, slug và nhiều biến thể về ký hiệu phiên bản và kiểu thân xe, nhưng một chuỗi mà nó chưa từng gặp sẽ trả về 404 vehicle_not_found (miễn phí, nhưng lãng phí một lượt gọi). Hãy liệt kê danh mục và lưu id của nó thay vì tự chuyển đổi chuỗi của riêng bạn, rồi tìm kiếm hoặc phân giải những trường hợp còn sót lại.