依据 API 文档所述行为编写的构建指南,不涉及任何具体客户。 发布于 。
问题:拍卖已上线,照片却还没到位
委托车辆一到齐,拍卖目录便会上线:每辆车都有一个 VIN、一个拍品编号和一个拍卖日期。照片则要等车辆抵达场地、摄影师也来到车旁才能拍摄,而一场拍卖中有部分车辆要到竞拍开始后才能拍到照片。在此之前,这些拍品都只是一行文字旁边的灰色方框。
目录渲染图能用正确的年款填补空位。但拍卖恰恰是渲染图绝不能被当作实车的场合:竞买人根据车况出价,而渲染图完全不体现车况,因为它并不描绘任何具体的实体车辆。下文的一切都源于同时兼顾这两点。
集成方式一览
- 根据 VIN 为每个拍品生成一张渲染图,仅在拍品自己的照片到位之前显示。
- 目录选择器,用于 VIN 无法解码的拍品。
- 图片上的标注:“目录渲染图,非本拍品实拍”。
- 每个拍品一个 URL,只生成一次,通过
Idempotency-Key生成,有效期与拍卖相同。
第 1 步:从拍品 VIN 到车辆 ID
GET /api/v1/vin/{vin} 免费。当目录中有该车辆时,返回结果会包含 data.vehicle.id,即要存储在拍品上的稳定 ID;解码出的配置、车身和发动机信息保留在拍品文字中。目录覆盖 1990 至 2027 年,因此拍卖中较老的库存车辆也能像最新车型一样渲染。保险指南逐字段讲解了解码结果,VIN 文档则介绍了部分 VIN 和错误处理。
# 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'第 2 步:没有可解码的 VIN?从目录中选择车辆
有些拍品没有可解码的 VIN:为其他市场生产的车辆、来源方录入错误的 VIN,或只写有年份、品牌和车型而别无其他信息的委托单。您可以在委托表单中提供三个下拉菜单,数据来自免费且公开的 GET /api/v1/vehicles:?year= 列出在该年份有车型的品牌,?year=&makeId= 列出该品牌的车型,每个车型都附带该车型年份的车辆 id。
# 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"
},
"…"
]
}
}同一份目录也以开源 @meterapp/vehicle-db 包的形式提供,因此下拉菜单也可以基于完全相同的列表离线运行。无论采用哪种方式,拍品最终保存的都是与解码 VIN 所得相同类型的 id,下游任何环节都无需手写车辆名称。
当整张委托拍卖清单一次性到达时,POST /api/v1/vehicles/check 每次调用最多读取 2,000 条记录,每条均按清单上的原样书写,并以拍品编号作为其 ref,然后逐条返回结果:匹配(附带需存储的 id)、建议、未匹配或无效行。不会渲染任何内容,也不会产生任何费用;详情请参阅车辆文档。
第 3 步:将渲染图标注为渲染图
请将标注直接放在图片上竞买人目光所及之处,并写入 alt 文本,而不只是放在下方的描述里。车况、里程、损伤、选装配置以及车辆的实际漆色应写在拍品说明中,有实拍照片后也应体现在照片里。实拍照片一到,就撤下渲染图。
<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>第 4 步:每件拍品一个 URL,只生成一次
在入库时使用 POST /api/v1/image-urls 生成拍品的 URL,并附带一个由拍卖场次和拍品组成的 Idempotency-Key,仅可包含字母、数字、.、_、: 和 -。URL 在生成时会将其图片计为当月的一张车辆图片,因此如果没有该键,重试入库会生成第二个 URL。使用该键时:
- 在 24 小时内,使用相同密钥和相同请求体的请求会重放第一次的响应(包括相同的 URL),并附带
Idempotent-Replayed: true,不会重复扣费。 - 相同的幂等键搭配不同的请求体会返回
422,而抢在首次请求之前完成的重试会返回409并附带Retry-After。因此,请在创建拍品时就确定请求体中的所有内容(包括有效期),并将其与拍品一起存储。 - 同时将 URL 存储在拍品上:密钥保障重试安全,而存储的 URL 则供页面在整个拍卖期间读取。
一次性导入整个目录时,每次调用最多可为 50 个拍品生成 URL,每批使用一个幂等键;响应中的每个 URL 都会回显其生成时对应的车辆和选项。
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第 5 步:让 URL 的有效期与拍卖时间匹配
ttl_seconds 最长可设为 604800(7 天);超过该值将被截断为上限。对于一周内结束的拍卖,请将其设为距结束的剩余时间。这样占位图会在竞价结束时同时失效,此后该 URL 将返回 403。
- 持续超过 7 天的限时拍卖:生成时设置
renew: true、将ttl_seconds设为最大值,并让renew_days覆盖整个拍卖期。续期只在其发生的月份将该图片计一次,若该图片当月已计过则不再重复计数;无人打开该拍品的那些周不产生任何计数,续期在renews_until时停止。 - 请将
max_uses保持为 0,即默认值:不限次数的 URL 在有效期内可被公开缓存,而设置了上限的 URL 为private, no-store,每次加载都会回源。 - 限制签发密钥:在控制台中将其限定为您拍卖网站的域名,这样被复制到其他网站的拍品图片将返回
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 arrive按拍卖规模选择套餐
每件拍品为一张车辆图片,即其车辆在其车漆和视角下的图片,除非两件拍品这三者完全相同;每个方案每月包含一定数量的车辆图片:免费版 100 张、Pro 版 2,500 张、Business 版 10,000 张;企业版另行协商。本月已计数的图片始终可正常提供;若某个批次会超出额度,则会在计数前被整体拒绝,响应中会注明方案和额度。请按您每月上架的拍品数量选择方案。交易平台案例介绍了如何在速率和渲染限制下控制大规模导入的节奏。
常见问题
- 拍品的实拍照片到位后,渲染图还能继续保留吗?
- 请替换。渲染图只是该车型年款的占位图,竞拍者永远不应需要猜测哪张图片才是待售车辆。如果页面保留它作参考,标注也必须一并保留。
- 拍卖结束后,URL 会怎样?
- 它会在有效期结束时过期,可续期的 URL 则会在续期期限届满时过期,之后返回 403。届时拍品已经有了自己的照片或拍卖结果,拍卖存档应展示这些照片。吊销用于生成这些 URL 的密钥,会使它们全部立即失效。
- 重试返回了 422,为什么?
- 同一个 Idempotency-Key 再次出现时附带了不同的请求体。常见原因是重试时根据当时的时钟重新计算了有效期。请在创建拍品时确定有效期并将其保存,这样重试时会发送相同的请求体,并重放第一次的响应。
- 渲染图能显示拍品的车漆颜色吗?
- 可以:color 支持任意预设名称或十六进制色值,因此图片可以与委托单上的车漆颜色一致,且不额外收费。但它仍然是该车漆的车型图,而非该拍品本身,因此标注需要保留。
- 一场包含 400 个拍品的拍卖需要多少费用?
- 当月 400 张车辆图片:每个拍品在其 URL 生成时计 1 张车辆图片,重试生成不会重复计数。URL 有效期内的每次加载均免费。各套餐每月包含的车辆图片:Free 100 张、Pro 2,500 张、Business 10,000 张;共享同一车辆、车漆和视角的拍品只计一次。