@meterapp/car-image-sdk 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -16
- package/dist/client.d.ts +62 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +181 -5
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +439 -131
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +393 -42
- package/dist/mcp/index.js.map +1 -1
- package/dist/params.d.ts +24 -2
- package/dist/params.d.ts.map +1 -1
- package/dist/params.js +92 -25
- package/dist/params.js.map +1 -1
- package/dist/types.d.ts +354 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/mcp/index.js
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
* entry point of the SDK has no runtime dependencies.
|
|
11
11
|
*/
|
|
12
12
|
import * as z from "zod";
|
|
13
|
-
import {
|
|
13
|
+
import { isColor } from "../params.js";
|
|
14
|
+
import { CATALOG, COLORS, CREDITS_PER_3D_MODEL, CREDITS_PER_DOLLAR, CREDITS_PER_IMAGE, DEFAULT_BASE_URL, FITS, FORMATS, FREE_CREDITS, IDEMPOTENCY_KEY_PATTERN, MAX_DIMENSION, MAX_PADDING_PERCENT, MIN_YEAR, MODEL_3D_FILE_KINDS, REFERRAL_SOURCES, REQUEST_FORMATS, REQUEST_KINDS, REQUEST_SORTS, REQUEST_STATUSES, SIZES, VEHICLE_ID_PATTERN, VIEWS, } from "../types.js";
|
|
14
15
|
import { SDK_VERSION } from "../version.js";
|
|
16
|
+
/** The 3D price as agents read it: "1,000 credits ($1.00)". */
|
|
17
|
+
const MODEL_3D_PRICE = `${CREDITS_PER_3D_MODEL.toLocaleString("en-US")} credits ($${(CREDITS_PER_3D_MODEL / CREDITS_PER_DOLLAR).toFixed(2)})`;
|
|
15
18
|
/**
|
|
16
19
|
* Catalog size as agents read it in the instructions and tool descriptions.
|
|
17
20
|
* Derived from CATALOG so the figures cannot drift from the database; agents
|
|
@@ -39,9 +42,13 @@ export const MCP_TOOLSETS = {
|
|
|
39
42
|
"create_car_image_urls",
|
|
40
43
|
"search_vehicles",
|
|
41
44
|
"resolve_vehicle",
|
|
45
|
+
"decode_vin",
|
|
42
46
|
"list_image_options",
|
|
43
47
|
"get_account",
|
|
44
48
|
"rate_image",
|
|
49
|
+
"create_3d_model",
|
|
50
|
+
"get_3d_model",
|
|
51
|
+
"publish_3d_model",
|
|
45
52
|
"describe_api",
|
|
46
53
|
],
|
|
47
54
|
all: [
|
|
@@ -49,9 +56,13 @@ export const MCP_TOOLSETS = {
|
|
|
49
56
|
"create_car_image_urls",
|
|
50
57
|
"search_vehicles",
|
|
51
58
|
"resolve_vehicle",
|
|
59
|
+
"decode_vin",
|
|
52
60
|
"list_image_options",
|
|
53
61
|
"get_account",
|
|
54
62
|
"rate_image",
|
|
63
|
+
"create_3d_model",
|
|
64
|
+
"get_3d_model",
|
|
65
|
+
"publish_3d_model",
|
|
55
66
|
"describe_api",
|
|
56
67
|
"list_requests",
|
|
57
68
|
"request_vehicle",
|
|
@@ -68,11 +79,11 @@ export const MCP_TOOLSET_NAMES = Object.keys(MCP_TOOLSETS);
|
|
|
68
79
|
export function isMcpToolset(value) {
|
|
69
80
|
return typeof value === "string" && MCP_TOOLSET_NAMES.includes(value);
|
|
70
81
|
}
|
|
71
|
-
const INSTRUCTIONS_COMMON = `Car Image API by Meter: studio-quality, transparent-background renders of any vehicle in the open @meterapp/vehicle-db catalog (${CATALOG_MAKES} makes, ${CATALOG_MODELS} models, model years ${CATALOG_YEARS}). Six camera views, fifteen
|
|
82
|
+
const INSTRUCTIONS_COMMON = `Car Image API by Meter: studio-quality, transparent-background renders of any vehicle in the open @meterapp/vehicle-db catalog (${CATALOG_MAKES} makes, ${CATALOG_MODELS} models, model years ${CATALOG_YEARS}). Six camera views, any paint color (fifteen named presets or any hex such as #1a2b3c), PNG/WebP/JPG up to 1024 px, any width and height (fit contain|cover|inside), transparent or a solid background color. Every catalog vehicle has a stable id (veh_ followed by 13 characters) that search_vehicles, resolve_vehicle and decode_vin return and every image or 3D tool accepts as \`vehicle\` in place of make, model and year.
|
|
72
83
|
|
|
73
|
-
Costs: every delivered image costs exactly ${CREDITS_PER_IMAGE} credit ($1 = ${CREDITS_PER_DOLLAR.toLocaleString("en-US")} credits; ${FREE_CREDITS} free credits per account), cold or CDN hit. create_car_image_urls charges 1 credit per URL when the URL is created; redemptions are free. search_vehicles, resolve_vehicle, list_image_options, get_account, rate_image and describe_api are free.
|
|
84
|
+
Costs: every delivered image costs exactly ${CREDITS_PER_IMAGE} credit ($1 = ${CREDITS_PER_DOLLAR.toLocaleString("en-US")} credits; ${FREE_CREDITS} free credits per account), cold or CDN hit. create_car_image_urls charges 1 credit per URL when the URL is created; redemptions are free. create_3d_model costs ${MODEL_3D_PRICE} per model, charged when the request is made -- unless the account already owns that vehicle in that color, in which case it is free (billing.already_owned is true): confirm with the human before calling it. search_vehicles, resolve_vehicle, decode_vin, list_image_options, get_account, rate_image, get_3d_model, publish_3d_model and describe_api are free.
|
|
74
85
|
|
|
75
|
-
Workflow: (1) resolve_vehicle turns free text such as "red 2024 porsche 911 side view" into exact parameters; search_vehicles browses the catalog. (2) Call get_car_image when the image itself is needed in the conversation. (3) For web pages, emails, documents or anything a browser will load, call create_car_image_urls and embed the signed URLs; they need no key.
|
|
86
|
+
Workflow: (1) resolve_vehicle turns free text such as "red 2024 porsche 911 side view" into exact parameters; search_vehicles browses the catalog; decode_vin turns a VIN into the catalog vehicle and its id. (2) Call get_car_image when the image itself is needed in the conversation. (3) For web pages, emails, documents or anything a browser will load, call create_car_image_urls and embed the signed URLs; they need no key. (4) For a textured 3D model (GLB, USDZ, FBX), call create_3d_model once the human has agreed to the cost, then poll get_3d_model every few seconds until status is ready and hand over files.<kind>.url; a model takes one to seven minutes. (5) To put a model on a web page, pass publish: true to create_3d_model (or call publish_3d_model later) and paste data.public.embed.html: a script tag and a <car-3d> element, hosted by Car Image, no key in the page; data.public.files are the key-free GLB, USDZ and poster URLs for any other viewer.
|
|
76
87
|
|
|
77
88
|
Security: never place the API key in URLs, HTML, logs, screenshots or client-side code. Signed delivery URLs are the only thing that should ever reach a browser.
|
|
78
89
|
|
|
@@ -92,16 +103,38 @@ const yearSchema = z.coerce
|
|
|
92
103
|
.int()
|
|
93
104
|
.min(MIN_YEAR)
|
|
94
105
|
.max(2100)
|
|
95
|
-
.describe(`Model year, e.g. 2024. The catalog covers ${MIN_YEAR} through next year.`);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
.describe(`Model year, e.g. 2024. The catalog covers ${MIN_YEAR} through next year. Omit when vehicle is given.`);
|
|
107
|
+
/** A paint color: a preset name, or a hex such as `#1a2b3c` (`1a2b3c` and `#rgb` are accepted too). */
|
|
108
|
+
export const colorSchema = z
|
|
109
|
+
.string()
|
|
110
|
+
.min(1)
|
|
111
|
+
.max(20)
|
|
112
|
+
.refine(isColor, { message: `color must be one of ${COLORS.join(", ")}, or a hex like #1a2b3c` })
|
|
113
|
+
.describe(`Paint color: one of the presets (${COLORS.join(", ")}) or a hex like #1a2b3c. Responses echo a hex as #rrggbb.`);
|
|
114
|
+
/** A stable catalog vehicle id, as every catalog tool returns it. */
|
|
115
|
+
export const vehicleIdSchema = z
|
|
116
|
+
.string()
|
|
117
|
+
.regex(VEHICLE_ID_PATTERN, "vehicle must be a vehicle id: veh_ followed by 13 characters")
|
|
118
|
+
.describe("Stable vehicle id such as veh_395yw8tn73ff8 (from search_vehicles, resolve_vehicle or decode_vin). Names the make, model and year, so omit those when it is given.");
|
|
119
|
+
const vehicleFields = {
|
|
120
|
+
vehicle: vehicleIdSchema.optional(),
|
|
121
|
+
make: z.string().min(1).max(80).optional().describe('Manufacturer, e.g. "Porsche", "BMW", "Toyota". Case and punctuation do not matter. Omit when vehicle is given.'),
|
|
122
|
+
model: z.string().min(1).max(80).optional().describe('Model as listed in the catalog, e.g. "911", "M3", "Corolla". Omit when vehicle is given.'),
|
|
123
|
+
year: yearSchema.optional(),
|
|
124
|
+
};
|
|
125
|
+
/** Either the vehicle id or all three of make, model and year; the server rejects both together. */
|
|
126
|
+
function namesOneVehicle(input) {
|
|
127
|
+
return input.vehicle !== undefined || (input.make !== undefined && input.model !== undefined && input.year !== undefined);
|
|
128
|
+
}
|
|
129
|
+
const ONE_VEHICLE = { message: "Give either vehicle (a veh_… id) or all of make, model and year" };
|
|
130
|
+
export const imageInputSchema = z
|
|
131
|
+
.object({
|
|
132
|
+
...vehicleFields,
|
|
100
133
|
view: z
|
|
101
134
|
.enum(VIEWS)
|
|
102
135
|
.default("front-3-4")
|
|
103
136
|
.describe("Camera angle: front (0°), front-3-4 (35°, the classic hero angle), side (profile, nose left), side-right (profile, nose right), rear (180°), rear-3-4 (145°)."),
|
|
104
|
-
color:
|
|
137
|
+
color: colorSchema.default("silver"),
|
|
105
138
|
size: z
|
|
106
139
|
.enum(["thumb", "small", "medium", "large"])
|
|
107
140
|
.optional()
|
|
@@ -129,7 +162,8 @@ export const imageInputSchema = z.object({
|
|
|
129
162
|
.enum(REQUEST_FORMATS)
|
|
130
163
|
.default("png")
|
|
131
164
|
.describe("png keeps the transparent background (default); webp is smallest; jpg is flattened onto white; auto lets each viewer's Accept header choose webp or png, negotiated on every load of a signed URL. An inline image (get_car_image) is delivered as png for auto."),
|
|
132
|
-
})
|
|
165
|
+
})
|
|
166
|
+
.refine(namesOneVehicle, ONE_VEHICLE);
|
|
133
167
|
/**
|
|
134
168
|
* The encoding an inline image (`get_car_image`) is delivered in. `auto` is
|
|
135
169
|
* negotiated from an HTTP `Accept` header, which a tool call does not carry,
|
|
@@ -204,11 +238,12 @@ export const MCP_TOOL_SCHEMAS = {
|
|
|
204
238
|
.max(128)
|
|
205
239
|
.optional()
|
|
206
240
|
.describe("The request_id returned by get_car_image or create_car_image_urls (preferred way to identify the image)."),
|
|
207
|
-
|
|
241
|
+
vehicle: vehicleIdSchema.optional(),
|
|
242
|
+
make: z.string().min(1).max(80).optional().describe("Alternative to request_id: identify the image by vehicle (make, model, year, or a vehicle id)."),
|
|
208
243
|
model: z.string().min(1).max(80).optional(),
|
|
209
244
|
year: z.coerce.number().int().min(MIN_YEAR).max(2100).optional(),
|
|
210
245
|
view: z.enum(VIEWS).optional(),
|
|
211
|
-
color:
|
|
246
|
+
color: colorSchema.optional(),
|
|
212
247
|
rating: z.number().int().min(1).max(5).optional().describe("1 (unusable) to 5 (perfect)."),
|
|
213
248
|
verdict: z.enum(["good", "bad"]).optional().describe("Quick thumbs up/down; provide this or rating."),
|
|
214
249
|
reason: z.string().max(500).optional().describe("What was wrong or right, e.g. 'wrong body style', 'perfect angle'."),
|
|
@@ -218,7 +253,44 @@ export const MCP_TOOL_SCHEMAS = {
|
|
|
218
253
|
.string()
|
|
219
254
|
.max(120)
|
|
220
255
|
.optional()
|
|
221
|
-
.describe('Filter by path fragment or keyword, e.g. "images/car", "image-urls", "
|
|
256
|
+
.describe('Filter by path fragment or keyword, e.g. "images/car", "image-urls", "vin", "3d". Omit for the full compact reference.'),
|
|
257
|
+
}),
|
|
258
|
+
decode_vin: z.object({
|
|
259
|
+
vin: z
|
|
260
|
+
.string()
|
|
261
|
+
.min(5)
|
|
262
|
+
.max(40)
|
|
263
|
+
.describe("A full 17-character VIN, or a partial VIN of at least 5 characters with * for each unknown position. Case, spaces and dashes do not matter."),
|
|
264
|
+
year: z.coerce.number().int().min(1900).max(2100).optional().describe("Model-year hint for the decoder; it flags a mismatch with the VIN's own year position."),
|
|
265
|
+
}),
|
|
266
|
+
create_3d_model: z
|
|
267
|
+
.object({
|
|
268
|
+
...vehicleFields,
|
|
269
|
+
color: colorSchema.default("silver"),
|
|
270
|
+
webhook_url: z
|
|
271
|
+
.string()
|
|
272
|
+
.url()
|
|
273
|
+
.max(2048)
|
|
274
|
+
.optional()
|
|
275
|
+
.describe("Public https URL that receives one POST {event: '3d_model.ready' | '3d_model.failed', data, sent_at} when the model settles; retried with backoff up to five times."),
|
|
276
|
+
webhook_secret: z.string().min(1).max(256).optional().describe("With webhook_url: the POST body is signed as X-CarImage-Signature: sha256=<hex HMAC of the raw body>."),
|
|
277
|
+
publish: z
|
|
278
|
+
.boolean()
|
|
279
|
+
.optional()
|
|
280
|
+
.describe("true publishes the model at once: data.public carries key-free URLs and a two-line embed (data.public.embed.html) whose files appear when the model is ready. Free; the same as publish_3d_model afterwards."),
|
|
281
|
+
idempotency_key: z
|
|
282
|
+
.string()
|
|
283
|
+
.regex(/^[A-Za-z0-9._:-]{1,255}$/, "idempotency_key must be 1-255 characters of letters, digits, '.', '_', ':' or '-'")
|
|
284
|
+
.optional()
|
|
285
|
+
.describe("A retry with the same key replays the first answer instead of charging again (24 h); use it when a call may be repeated."),
|
|
286
|
+
})
|
|
287
|
+
.refine(namesOneVehicle, ONE_VEHICLE),
|
|
288
|
+
get_3d_model: z.object({
|
|
289
|
+
id: z.string().uuid().describe("The 3D request id returned by create_3d_model."),
|
|
290
|
+
}),
|
|
291
|
+
publish_3d_model: z.object({
|
|
292
|
+
id: z.string().uuid().describe("The 3D request id returned by create_3d_model."),
|
|
293
|
+
unpublish: z.boolean().optional().describe("true takes the model down again: its public URLs stop working within the hour and data.public becomes null. Default false (publish)."),
|
|
222
294
|
}),
|
|
223
295
|
list_requests: z.object({
|
|
224
296
|
kind: z.enum(REQUEST_KINDS).optional().describe("Only vehicle requests or only feature requests. Omit for both."),
|
|
@@ -281,11 +353,14 @@ export const MCP_TOOL_SCHEMAS = {
|
|
|
281
353
|
*/
|
|
282
354
|
/** The vehicle echoed back with an image: exactly what would render it again. */
|
|
283
355
|
const vehicleDescriptorSchema = z.looseObject({
|
|
356
|
+
/** Stable id of the catalog vehicle; null when the catalog cannot name one. */
|
|
357
|
+
vehicle_id: z.string().nullable(),
|
|
284
358
|
make: z.string(),
|
|
285
359
|
model: z.string(),
|
|
286
360
|
year: z.number().int(),
|
|
287
361
|
view: z.enum(VIEWS),
|
|
288
|
-
|
|
362
|
+
/** A preset name, or a hex paint as #rrggbb. */
|
|
363
|
+
color: z.string(),
|
|
289
364
|
width: z.number().int().optional(),
|
|
290
365
|
height: z.number().int().optional(),
|
|
291
366
|
fit: z.enum(FITS).optional(),
|
|
@@ -355,6 +430,101 @@ const createdRequestResult = z.looseObject({
|
|
|
355
430
|
deduplicated: z.boolean(),
|
|
356
431
|
request_id: z.string(),
|
|
357
432
|
});
|
|
433
|
+
/** The response of GET /api/v1/vin/{vin}: every key present, unknown values null. */
|
|
434
|
+
const vinResultSchema = z.looseObject({
|
|
435
|
+
vin: z.string(),
|
|
436
|
+
valid: z.boolean(),
|
|
437
|
+
errors: z.array(z.looseObject({ code: z.number().int(), text: z.string() })),
|
|
438
|
+
suggested_vin: z.string().nullable(),
|
|
439
|
+
year: z.number().int().nullable(),
|
|
440
|
+
make: z.string().nullable(),
|
|
441
|
+
model: z.string().nullable(),
|
|
442
|
+
trim: z.string().nullable(),
|
|
443
|
+
series: z.string().nullable(),
|
|
444
|
+
body_class: z.string().nullable(),
|
|
445
|
+
vehicle_type: z.string().nullable(),
|
|
446
|
+
doors: z.number().int().nullable(),
|
|
447
|
+
drive_type: z.string().nullable(),
|
|
448
|
+
fuel_type: z.string().nullable(),
|
|
449
|
+
engine: z.looseObject({
|
|
450
|
+
cylinders: z.number().int().nullable(),
|
|
451
|
+
displacement_l: z.number().nullable(),
|
|
452
|
+
hp: z.number().nullable(),
|
|
453
|
+
model: z.string().nullable(),
|
|
454
|
+
}),
|
|
455
|
+
transmission: z.looseObject({ style: z.string().nullable(), speeds: z.number().int().nullable() }),
|
|
456
|
+
manufacturer: z.string().nullable(),
|
|
457
|
+
plant: z.looseObject({ city: z.string().nullable(), state: z.string().nullable(), country: z.string().nullable() }),
|
|
458
|
+
gvwr: z.string().nullable(),
|
|
459
|
+
/** Every decoded vPIC variable verbatim. */
|
|
460
|
+
attributes: z.record(z.string(), z.string()),
|
|
461
|
+
/** The catalog vehicle the VIN maps to, with the id and path that render it; null when the catalog lacks it. */
|
|
462
|
+
vehicle: z.looseObject({ id: z.string(), make: z.string(), model: z.string(), year: z.number().int(), image_path: z.string() }).nullable(),
|
|
463
|
+
source: z.enum(["vpic-db", "vpic-api"]),
|
|
464
|
+
});
|
|
465
|
+
const model3dFileSchema = z.looseObject({
|
|
466
|
+
/** API URL that redirects to a short-lived signed download; needs the key. */
|
|
467
|
+
url: z.string(),
|
|
468
|
+
bytes: z.number().int(),
|
|
469
|
+
content_type: z.string(),
|
|
470
|
+
});
|
|
471
|
+
const model3dPublicFileSchema = z.looseObject({
|
|
472
|
+
/** Stable, key-free URL that redirects to the CDN copy. */
|
|
473
|
+
url: z.string(),
|
|
474
|
+
bytes: z.number().int(),
|
|
475
|
+
content_type: z.string(),
|
|
476
|
+
});
|
|
477
|
+
/** The hosted side of a published request: key-free URLs and the embed. */
|
|
478
|
+
const model3dPublicSchema = z.looseObject({
|
|
479
|
+
id: z.string(),
|
|
480
|
+
url: z.string(),
|
|
481
|
+
/** Null until the model is ready and its copies were made. */
|
|
482
|
+
files: z.looseObject({ glb: model3dPublicFileSchema, usdz: model3dPublicFileSchema.optional(), poster: model3dPublicFileSchema.optional() }).nullable(),
|
|
483
|
+
embed: z.looseObject({ script: z.string(), html: z.string() }),
|
|
484
|
+
published_at: z.string(),
|
|
485
|
+
});
|
|
486
|
+
/** A 3D request as POST /api/v1/3d and GET /api/v1/3d/{id} return it. */
|
|
487
|
+
const model3dSchema = z.looseObject({
|
|
488
|
+
id: z.string(),
|
|
489
|
+
object: z.literal("3d_model"),
|
|
490
|
+
status: z.enum(["queued", "processing", "ready", "failed"]),
|
|
491
|
+
/** 0-100. */
|
|
492
|
+
progress: z.number(),
|
|
493
|
+
stage: z.string().nullable(),
|
|
494
|
+
vehicle: z.looseObject({ id: z.string().nullable(), make: z.string(), model: z.string(), year: z.number().int() }),
|
|
495
|
+
/** A preset name, or a hex paint as #rrggbb. */
|
|
496
|
+
color: z.string(),
|
|
497
|
+
generator: z.string(),
|
|
498
|
+
/** Present once status is ready. */
|
|
499
|
+
files: z
|
|
500
|
+
.looseObject({
|
|
501
|
+
glb: model3dFileSchema.optional(),
|
|
502
|
+
/** The GLB rebuilt for browsers: meshopt, WebP textures, about a tenth of the bytes. */
|
|
503
|
+
glb_web: model3dFileSchema.optional(),
|
|
504
|
+
usdz: model3dFileSchema.optional(),
|
|
505
|
+
fbx: model3dFileSchema.optional(),
|
|
506
|
+
thumbnail: model3dFileSchema.optional(),
|
|
507
|
+
})
|
|
508
|
+
.nullable(),
|
|
509
|
+
polycount: z.number().int().nullable(),
|
|
510
|
+
/** Set when status is failed; the request was refunded. */
|
|
511
|
+
error: z.looseObject({ code: z.string(), message: z.string() }).nullable(),
|
|
512
|
+
estimated_seconds_remaining: z.number().nullable(),
|
|
513
|
+
webhook: z
|
|
514
|
+
.looseObject({
|
|
515
|
+
url: z.string(),
|
|
516
|
+
status: z.string().nullable(),
|
|
517
|
+
attempts: z.number().int(),
|
|
518
|
+
last_status: z.number().int().nullable(),
|
|
519
|
+
delivered_at: z.string().nullable(),
|
|
520
|
+
})
|
|
521
|
+
.nullable(),
|
|
522
|
+
/** Key-free URLs and the embed once published; null until then. */
|
|
523
|
+
public: model3dPublicSchema.nullable(),
|
|
524
|
+
created_at: z.string(),
|
|
525
|
+
updated_at: z.string(),
|
|
526
|
+
ready_at: z.string().nullable(),
|
|
527
|
+
});
|
|
358
528
|
export const MCP_TOOL_OUTPUT_SCHEMAS = {
|
|
359
529
|
get_car_image: z.looseObject({
|
|
360
530
|
credits_charged: z.number().nullable(),
|
|
@@ -396,11 +566,13 @@ export const MCP_TOOL_OUTPUT_SCHEMAS = {
|
|
|
396
566
|
data: z.looseObject({
|
|
397
567
|
/** Ready to pass straight to get_car_image. */
|
|
398
568
|
params: z.looseObject({
|
|
569
|
+
/** Stable id of the resolved vehicle; pass it as `vehicle` to render it again. */
|
|
570
|
+
vehicle_id: z.string().nullable(),
|
|
399
571
|
make: z.string(),
|
|
400
572
|
model: z.string(),
|
|
401
573
|
year: z.number().int(),
|
|
402
574
|
view: z.enum(VIEWS),
|
|
403
|
-
color: z.
|
|
575
|
+
color: z.string(),
|
|
404
576
|
}),
|
|
405
577
|
display: z.looseObject({ make_name: z.string(), model_name: z.string() }).optional(),
|
|
406
578
|
/** Other catalog vehicles that fit the phrase; ask when confidence is low. */
|
|
@@ -465,6 +637,20 @@ export const MCP_TOOL_OUTPUT_SCHEMAS = {
|
|
|
465
637
|
}),
|
|
466
638
|
request_id: z.string(),
|
|
467
639
|
}),
|
|
640
|
+
decode_vin: z.looseObject({ data: vinResultSchema, request_id: z.string() }),
|
|
641
|
+
create_3d_model: z.looseObject({
|
|
642
|
+
data: model3dSchema,
|
|
643
|
+
billing: z.looseObject({
|
|
644
|
+
charged_on: z.literal("creation"),
|
|
645
|
+
credits_charged: z.number(),
|
|
646
|
+
credits_remaining: z.number(),
|
|
647
|
+
/** True, with credits_charged 0, when the account already owned this vehicle in this color. */
|
|
648
|
+
already_owned: z.boolean(),
|
|
649
|
+
}),
|
|
650
|
+
request_id: z.string(),
|
|
651
|
+
}),
|
|
652
|
+
get_3d_model: z.looseObject({ data: model3dSchema, request_id: z.string() }),
|
|
653
|
+
publish_3d_model: z.looseObject({ data: model3dSchema, request_id: z.string() }),
|
|
468
654
|
list_requests: requestListResult,
|
|
469
655
|
request_vehicle: createdRequestResult,
|
|
470
656
|
request_feature: createdRequestResult,
|
|
@@ -526,11 +712,44 @@ export function toImageOptions(data) {
|
|
|
526
712
|
},
|
|
527
713
|
};
|
|
528
714
|
}
|
|
715
|
+
/** The one-line summary both servers put above a decoded VIN. */
|
|
716
|
+
export function vinSummary(result) {
|
|
717
|
+
const car = [result.year ?? "?", result.make ?? "unknown make", result.model ?? "", result.trim ?? ""].filter(Boolean).join(" ");
|
|
718
|
+
const notes = result.errors.length ? ` Decoder notes: ${result.errors.map((error) => error.text).join("; ")}.` : "";
|
|
719
|
+
if (result.vehicle) {
|
|
720
|
+
return `${result.vin}: ${car}. Catalog vehicle ${result.vehicle.id} (${result.vehicle.year} ${result.vehicle.make} ${result.vehicle.model}): pass it as vehicle to get_car_image, create_car_image_urls or create_3d_model.${notes}`;
|
|
721
|
+
}
|
|
722
|
+
return `${result.vin}: ${car}. Not in the image catalog; search_vehicles may find the model under another spelling.${notes}`;
|
|
723
|
+
}
|
|
724
|
+
/** The one-line summary both servers put above a 3D model, with the charge when it was just created. */
|
|
725
|
+
export function model3dSummary(model, billing) {
|
|
726
|
+
const car = `${model.vehicle.year} ${model.vehicle.make} ${model.vehicle.model} in ${model.color}`;
|
|
727
|
+
const charge = billing
|
|
728
|
+
? billing.already_owned
|
|
729
|
+
? `Already owned: nothing charged, ${billing.credits_remaining.toLocaleString("en-US")} credits remaining. `
|
|
730
|
+
: `Charged ${billing.credits_charged.toLocaleString("en-US")} credits, ${billing.credits_remaining.toLocaleString("en-US")} remaining. `
|
|
731
|
+
: "";
|
|
732
|
+
const hosted = model.public
|
|
733
|
+
? ` Published as ${model.public.id}: paste public.embed.html into any page (no key), or use public.files for the key-free GLB, USDZ and poster URLs${model.public.files ? "" : ", live once the model is ready"}.`
|
|
734
|
+
: "";
|
|
735
|
+
if (model.status === "ready") {
|
|
736
|
+
const files = Object.entries(model.files ?? {})
|
|
737
|
+
.filter((entry) => Boolean(entry[1]))
|
|
738
|
+
.map(([kind, file]) => `${kind} (${(file.bytes / 1_048_576).toFixed(1)} MB)`)
|
|
739
|
+
.join(", ");
|
|
740
|
+
return `${charge}3D model ${model.id} is ready: ${car}; files ${files || "none"}. Each files.<kind>.url redirects to a signed download valid for an hour (the API key is needed on the first hop only).${hosted}`;
|
|
741
|
+
}
|
|
742
|
+
if (model.status === "failed") {
|
|
743
|
+
return `${charge}3D model ${model.id} failed: ${model.error?.message ?? "the model could not be generated"}. The credits were refunded; try again later or with another color.`;
|
|
744
|
+
}
|
|
745
|
+
const eta = model.estimated_seconds_remaining === null ? "" : `, about ${model.estimated_seconds_remaining} s left`;
|
|
746
|
+
return `${charge}3D model ${model.id} is ${model.status} (${model.progress}%${model.stage ? `, ${model.stage}` : ""}${eta}): ${car}. Poll get_3d_model(id) again in a few seconds; do not create it twice.${hosted}`;
|
|
747
|
+
}
|
|
529
748
|
export const MCP_TOOLS = {
|
|
530
749
|
get_car_image: {
|
|
531
750
|
name: "get_car_image",
|
|
532
751
|
title: "Get car image",
|
|
533
|
-
description: `Render or fetch a studio-quality, transparent-background image of a specific vehicle (make, model,
|
|
752
|
+
description: `Render or fetch a studio-quality, transparent-background image of a specific vehicle (make, model and year, or its vehicle id) from one of six camera angles in any paint color (fifteen named presets or any hex such as #1a2b3c), at any width and height up to ${MAX_DIMENSION} px (fit contain|cover|inside), transparent or on a solid background, optionally trimmed to the car. Returns the image inline (PNG/WebP/JPG; format auto delivers PNG here) plus usage metadata (credits_charged, credits_remaining, source cache|generated, request_id, the vehicle with its vehicle_id). COSTS ${CREDITS_PER_IMAGE} CREDIT per call, cached or generated. Use resolve_vehicle first when the request is free text; use create_car_image_urls instead when the image must be embedded in a web page or document.`,
|
|
534
753
|
inputSchema: MCP_TOOL_SCHEMAS.get_car_image,
|
|
535
754
|
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.get_car_image,
|
|
536
755
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
@@ -538,7 +757,7 @@ export const MCP_TOOLS = {
|
|
|
538
757
|
create_car_image_urls: {
|
|
539
758
|
name: "create_car_image_urls",
|
|
540
759
|
title: "Create signed car image URLs",
|
|
541
|
-
description: "Create 1-50 signed, key-free delivery URLs that browsers, emails or documents can load directly (<img src>). Each URL costs 1 credit at creation; redemptions within the TTL are free and publicly cacheable. Each image takes the same sizing options
|
|
760
|
+
description: "Create 1-50 signed, key-free delivery URLs that browsers, emails or documents can load directly (<img src>). Each URL costs 1 credit at creation; redemptions within the TTL are free and publicly cacheable. Each image names its vehicle like get_car_image (make, model, year, or a vehicle id), takes any paint color (a preset or a hex) and the same sizing options (width, height, fit, background, trim, padding); format auto makes the URL serve webp or png per viewer. For email, CMS pages or documents that outlive a TTL, set renew: true with ttl_seconds 604800: the URL keeps serving past expiry at one more credit per opened week, for up to renew_days. Pass idempotency_key when the call might be repeated: a retry with the same key and arguments replays the first result instead of billing again. Returns id, url, expires_at, max_uses, renews_until and the normalized vehicle for each image. Prefer this over get_car_image whenever the output is HTML, Markdown, a document or a website.",
|
|
542
761
|
inputSchema: MCP_TOOL_SCHEMAS.create_car_image_urls,
|
|
543
762
|
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.create_car_image_urls,
|
|
544
763
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
@@ -546,7 +765,7 @@ export const MCP_TOOLS = {
|
|
|
546
765
|
search_vehicles: {
|
|
547
766
|
name: "search_vehicles",
|
|
548
767
|
title: "Search the vehicle catalog",
|
|
549
|
-
description: `Free. Fuzzy-search the open vehicle catalog (${CATALOG_MAKES} makes, ${CATALOG_MODELS} models, ${CATALOG_YEARS}) by make and/or model with typo tolerance. Returns canonical make/model names, available years and match kind, ready to pass to get_car_image. Use it to confirm a vehicle exists or to list a make's models before rendering.`,
|
|
768
|
+
description: `Free. Fuzzy-search the open vehicle catalog (${CATALOG_MAKES} makes, ${CATALOG_MODELS} models, ${CATALOG_YEARS}) by make and/or model with typo tolerance. Returns canonical make/model names, available years and match kind, ready to pass to get_car_image (a model hit also carries stable vehicle ids per year, usable as \`vehicle\`). Use it to confirm a vehicle exists or to list a make's models before rendering.`,
|
|
550
769
|
inputSchema: MCP_TOOL_SCHEMAS.search_vehicles,
|
|
551
770
|
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.search_vehicles,
|
|
552
771
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
@@ -554,7 +773,7 @@ export const MCP_TOOLS = {
|
|
|
554
773
|
resolve_vehicle: {
|
|
555
774
|
name: "resolve_vehicle",
|
|
556
775
|
title: "Resolve free text to image parameters",
|
|
557
|
-
description: 'Free. Turn a natural-language request such as "red 2024 porsche 911 side view" into exact get_car_image parameters (make, model, year, view, color) plus alternative candidates and a confidence of high, medium or low. Call this before rendering when the user did not spell out the parameters; ask the user to choose when confidence is low or several candidates fit.',
|
|
776
|
+
description: 'Free. Turn a natural-language request such as "red 2024 porsche 911 side view" into exact get_car_image parameters (vehicle_id, make, model, year, view, color) plus alternative candidates and a confidence of high, medium or low. Call this before rendering when the user did not spell out the parameters; ask the user to choose when confidence is low or several candidates fit.',
|
|
558
777
|
inputSchema: MCP_TOOL_SCHEMAS.resolve_vehicle,
|
|
559
778
|
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.resolve_vehicle,
|
|
560
779
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
@@ -562,7 +781,7 @@ export const MCP_TOOLS = {
|
|
|
562
781
|
list_image_options: {
|
|
563
782
|
name: "list_image_options",
|
|
564
783
|
title: "List image options",
|
|
565
|
-
description: "Free. List the supported camera views (with yaw angles and aliases), the fifteen preset colors with hex swatches, size presets, output formats, current pricing and catalog coverage. Use it when you need to validate or explain what can be requested.",
|
|
784
|
+
description: "Free. List the supported camera views (with yaw angles and aliases), the fifteen preset colors with hex swatches (any other paint is a hex color), size presets, output formats, current pricing and catalog coverage. Use it when you need to validate or explain what can be requested.",
|
|
566
785
|
inputSchema: MCP_TOOL_SCHEMAS.list_image_options,
|
|
567
786
|
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.list_image_options,
|
|
568
787
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
@@ -578,7 +797,7 @@ export const MCP_TOOLS = {
|
|
|
578
797
|
rate_image: {
|
|
579
798
|
name: "rate_image",
|
|
580
799
|
title: "Rate a delivered image",
|
|
581
|
-
description: "Free. Record quality feedback on a delivered image, either by the request_id returned with it or by vehicle (make, model, year,
|
|
800
|
+
description: "Free. Record quality feedback on a delivered image, either by the request_id returned with it or by vehicle (make, model, year, or a vehicle id, plus view and color). Provide verdict good|bad or rating 1-5 and an optional reason. Feedback drives re-renders and prompt improvements.",
|
|
582
801
|
inputSchema: MCP_TOOL_SCHEMAS.rate_image,
|
|
583
802
|
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.rate_image,
|
|
584
803
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
@@ -590,6 +809,38 @@ export const MCP_TOOLS = {
|
|
|
590
809
|
inputSchema: MCP_TOOL_SCHEMAS.describe_api,
|
|
591
810
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
592
811
|
},
|
|
812
|
+
decode_vin: {
|
|
813
|
+
name: "decode_vin",
|
|
814
|
+
title: "Decode a VIN",
|
|
815
|
+
description: "Free. Decode a full 17-character VIN, or a partial VIN of at least 5 characters with * for unknown positions, with NHTSA vPIC data: year, make, model, trim, series, body class, engine, transmission, manufacturer, plant and every decoded attribute. `vehicle` is the catalog vehicle it maps to, with the stable id to pass as `vehicle` to get_car_image, create_car_image_urls or create_3d_model, or null when the catalog does not carry that car. Use it whenever the human gives a VIN instead of a make and model.",
|
|
816
|
+
inputSchema: MCP_TOOL_SCHEMAS.decode_vin,
|
|
817
|
+
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.decode_vin,
|
|
818
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
819
|
+
},
|
|
820
|
+
create_3d_model: {
|
|
821
|
+
name: "create_3d_model",
|
|
822
|
+
title: "Create a 3D model",
|
|
823
|
+
description: `COSTS ${MODEL_3D_PRICE} PER CALL for a vehicle and color the account does not own yet, charged when the request is made whether the model is cached, in progress or new: confirm with the human before creating one. A vehicle and color the account already holds a live request for is owned and free (billing.already_owned true, credits_charged 0). Build a textured 3D model (GLB, USDZ, FBX and a PNG thumbnail, plus glb_web, a browser build a tenth the size) of one catalog vehicle (make, model and year, or a vehicle id) in one paint color (a preset or any hex), from the same renders the images use, so it matches the pictures. A failed model is refunded. Returns the request at once with status queued, processing or ready (cached), progress 0-100 and estimated_seconds_remaining; poll get_3d_model every few seconds until status is ready, then hand over files.<kind>.url (API URLs that redirect to short-lived signed downloads), or pass webhook_url to be POSTed the same JSON when it settles. Pass publish: true when the model is for a web page: data.public.embed.html is a two-line, key-free embed hosted by Car Image. A model takes one to seven minutes.`,
|
|
824
|
+
inputSchema: MCP_TOOL_SCHEMAS.create_3d_model,
|
|
825
|
+
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.create_3d_model,
|
|
826
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
827
|
+
},
|
|
828
|
+
get_3d_model: {
|
|
829
|
+
name: "get_3d_model",
|
|
830
|
+
title: "Get a 3D model",
|
|
831
|
+
description: "Free. The state of one 3D request by id: status (queued, processing, ready, failed), progress 0-100, stage, estimated_seconds_remaining, the vehicle and color, once ready the files (glb, glb_web, usdz, fbx, thumbnail) with their download URLs, byte sizes and content types, and public (key-free URLs and the embed) when the model is published. Poll it every few seconds after create_3d_model; a processing model is re-checked with the provider on each call. A failed model carries error and was refunded.",
|
|
832
|
+
inputSchema: MCP_TOOL_SCHEMAS.get_3d_model,
|
|
833
|
+
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.get_3d_model,
|
|
834
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
835
|
+
},
|
|
836
|
+
publish_3d_model: {
|
|
837
|
+
name: "publish_3d_model",
|
|
838
|
+
title: "Publish a 3D model",
|
|
839
|
+
description: "Free. Publish one of the account's 3D requests so a web page can show it without an API key: data.public gains an id, key-free URLs for the web GLB, the USDZ and the poster (served from Car Image's CDN) and embed.html, a script tag plus a <car-3d> element to paste into any HTML. Works before the model is ready (the URLs are stable, the files follow). Pass unpublish: true to take it down again; the links stop working within the hour. Idempotent either way.",
|
|
840
|
+
inputSchema: MCP_TOOL_SCHEMAS.publish_3d_model,
|
|
841
|
+
outputSchema: MCP_TOOL_OUTPUT_SCHEMAS.publish_3d_model,
|
|
842
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
843
|
+
},
|
|
593
844
|
list_requests: {
|
|
594
845
|
name: "list_requests",
|
|
595
846
|
title: "List vehicle and feature requests",
|
|
@@ -665,11 +916,12 @@ export const API_REFERENCE = [
|
|
|
665
916
|
auth: "bearer",
|
|
666
917
|
credits: "1 per call (cached or generated)",
|
|
667
918
|
params: [
|
|
668
|
-
"make (required)",
|
|
669
|
-
"model (required)",
|
|
670
|
-
`year (required, ${CATALOG_YEARS})`,
|
|
919
|
+
"make (required unless vehicle is given)",
|
|
920
|
+
"model (required unless vehicle is given)",
|
|
921
|
+
`year (required unless vehicle is given, ${CATALOG_YEARS})`,
|
|
922
|
+
"vehicle (a stable vehicle id such as veh_395yw8tn73ff8, from /api/v1/vehicles, /api/v1/vin or resolve; replaces make, model and year, never alongside them)",
|
|
671
923
|
`view (${VIEWS.join("|")}; default front-3-4)`,
|
|
672
|
-
`color (${COLORS.join("|")}; default silver)`,
|
|
924
|
+
`color (${COLORS.join("|")}, or any hex paint written bare in URLs such as 1a2b3c; default silver; JSON echoes a hex as #1a2b3c)`,
|
|
673
925
|
"size (thumb=256|small=512|medium=768|large=1024)",
|
|
674
926
|
`w, h (1-${MAX_DIMENSION} px; both -> the output is exactly w x h)`,
|
|
675
927
|
`fit (${FITS.join("|")}; default contain; only matters with both w and h)`,
|
|
@@ -677,11 +929,11 @@ export const API_REFERENCE = [
|
|
|
677
929
|
`trim (1 crops to the car's alpha bounds before sizing), padding (0-${MAX_PADDING_PERCENT} percent, only with trim)`,
|
|
678
930
|
`format (${FORMATS.join("|")}|auto; default png; auto negotiates webp/png from Accept)`,
|
|
679
931
|
],
|
|
680
|
-
returns: "image bytes; headers X-Credits-Charged, X-Credits-Remaining, X-Image-Source (cache|generated), X-Image-Width, X-Image-Height, ETag, X-Request-Id (Vary: Accept with format=auto). With Accept: application/json: {data:{url,expires_at,vehicle,width,height,format}, billing:{credits_charged,credits_remaining}, request_id}",
|
|
681
|
-
errors: ["400 invalid params", "401 missing/invalid key", "402 insufficient credits (balance, required_credits)", "404 vehicle not in catalog", "429 rate limited (Retry-After)", "502 render failed (refunded)"],
|
|
932
|
+
returns: "image bytes; headers X-Credits-Charged, X-Credits-Remaining, X-Image-Source (cache|generated), X-Image-Width, X-Image-Height, ETag, X-Request-Id (Vary: Accept with format=auto). With Accept: application/json: {data:{url,expires_at,vehicle:{vehicle_id,make,model,year,view,color,…},width,height,format}, billing:{credits_charged,credits_remaining}, request_id}",
|
|
933
|
+
errors: ["400 invalid params (also vehicle together with make/model/year)", "401 missing/invalid key", "402 insufficient credits (balance, required_credits)", "404 vehicle not in catalog or unknown vehicle id", "429 rate limited (Retry-After)", "502 render failed (refunded)"],
|
|
682
934
|
notes: "Do not purchase credits automatically on 402; ask the human. Respect Retry-After on 429.",
|
|
683
|
-
sdk: "client.getImage({ make, model, year, view, color, size, width, height, fit, background, trim, padding, format }) / client.getImageUrl(...)",
|
|
684
|
-
cli: "car-image get --make Porsche --model 911 --year 2024 --view side --color red --out car.png",
|
|
935
|
+
sdk: "client.getImage({ make, model, year, view, color, size, width, height, fit, background, trim, padding, format }) / client.getImage({ vehicle: 'veh_…', color: '#1a2b3c' }) / client.getImageUrl(...)",
|
|
936
|
+
cli: "car-image get --make Porsche --model 911 --year 2024 --view side --color red --out car.png · car-image get --vehicle veh_395yw8tn73ff8 --color 1a2b3c",
|
|
685
937
|
},
|
|
686
938
|
{
|
|
687
939
|
method: "POST",
|
|
@@ -689,10 +941,10 @@ export const API_REFERENCE = [
|
|
|
689
941
|
summary: "Create 1-50 signed delivery URLs for browsers (no key needed to load them).",
|
|
690
942
|
auth: "bearer",
|
|
691
943
|
credits: "1 per URL, charged at creation; redemptions are free",
|
|
692
|
-
params: ["images: [{make, model, year, view?, color
|
|
693
|
-
returns: "201 {data:[{id,url,expires_at,max_uses,renews_until,vehicle}], billing:{charged_on:'creation',credits_charged,credits_remaining,credits_per_url,renewal?}, request_id}; a replay carries Idempotent-Replayed: true",
|
|
944
|
+
params: ["images: [{make, model, year | vehicle, view?, color? (preset or hex), size?, width?, height?, fit?, background?, trim?, padding?, format?}] (or a single image object)", "ttl_seconds (60-604800; default 3600)", "max_uses (0 = unlimited; default 0)", "renew (auto-renew past the TTL at 1 credit per opened window; requires max_uses 0)", "renew_days (1-365; default 365)", "header Idempotency-Key (optional; a retry with the same key and body replays the first response instead of billing again)"],
|
|
945
|
+
returns: "201 {data:[{id,url,expires_at,max_uses,renews_until,vehicle:{vehicle_id,…}}], billing:{charged_on:'creation',credits_charged,credits_remaining,credits_per_url,renewal?}, request_id}; a replay carries Idempotent-Replayed: true",
|
|
694
946
|
errors: ["400 invalid body or batch > 50", "401", "402", "404 vehicle not in catalog", "409 same Idempotency-Key still in flight", "422 same Idempotency-Key with a different body", "429"],
|
|
695
|
-
sdk: "client.createImageUrls([{ make, model, year, ...sizing }], { ttlSeconds, maxUses, renew, renewDays, idempotencyKey })",
|
|
947
|
+
sdk: "client.createImageUrls([{ make, model, year, ...sizing }, { vehicle: 'veh_…' }], { ttlSeconds, maxUses, renew, renewDays, idempotencyKey })",
|
|
696
948
|
cli: "car-image url --make BMW --model M3 --year 2022 --ttl 86400 [--renew] [--idempotency-key <key>]",
|
|
697
949
|
},
|
|
698
950
|
{
|
|
@@ -710,7 +962,7 @@ export const API_REFERENCE = [
|
|
|
710
962
|
summary: "Views, colors, sizes, formats, pricing and catalog coverage.",
|
|
711
963
|
auth: "public",
|
|
712
964
|
credits: "free",
|
|
713
|
-
returns: "{data:{views:[{id,label,yaw_degrees,description,aliases}], colors:[{name,hex}], sizes:{presets,max,source}, fits, default_fit, backgrounds, trim, formats, pricing:{credits_per_image,credits_per_dollar,free_credits}, catalog:{makes,models,years,sources}}}",
|
|
965
|
+
returns: "{data:{views:[{id,label,yaw_degrees,description,aliases}], colors:[{name,hex}], paint:{parameter,custom,example}, sizes:{presets,max,source}, fits, default_fit, backgrounds, trim, formats, pricing:{credits_per_image,credits_per_dollar,free_credits}, catalog:{makes,models,years,sources}, vehicle_ids:{description,example,lookup,parameter}}}",
|
|
714
966
|
sdk: "client.options()",
|
|
715
967
|
cli: "car-image options",
|
|
716
968
|
},
|
|
@@ -721,28 +973,122 @@ export const API_REFERENCE = [
|
|
|
721
973
|
auth: "bearer",
|
|
722
974
|
credits: "free",
|
|
723
975
|
params: ['query: "red 2024 porsche 911 side view"'],
|
|
724
|
-
returns: "{data:{params:{make,model,year,view,color}, display:{make_name,model_name}, candidates:[...], confidence:high|medium|low, image_path}}",
|
|
976
|
+
returns: "{data:{params:{vehicle_id,make,model,year,view,color}, display:{make_name,model_name}, candidates:[...], confidence:high|medium|low, image_path}}",
|
|
725
977
|
sdk: "client.resolve(query)",
|
|
726
978
|
cli: "car-image resolve red 2024 porsche 911 side view",
|
|
727
979
|
},
|
|
728
980
|
{
|
|
729
981
|
method: "GET",
|
|
730
982
|
path: "/api/v1/vehicles",
|
|
731
|
-
summary: "Browse or search the catalog.",
|
|
983
|
+
summary: "Browse or search the catalog; every model in a known year carries its stable vehicle id.",
|
|
732
984
|
auth: "public",
|
|
733
985
|
credits: "free",
|
|
734
|
-
params: ["(none) -> years", "year -> makes [{id,name}]", "year + makeId -> models", "q (+ limit) -> fuzzy search results"],
|
|
735
|
-
returns: "{data:{years}} | {data:{year,makes}} | {data:{year,make_id,models}} | {data:{results}}",
|
|
736
|
-
sdk: "client.vehicles({ year, makeId }) / client.searchVehicles(q, { limit })",
|
|
986
|
+
params: ["(none) -> years", "year -> makes [{id,name,slug}]", "year + makeId -> models [{name,slug,vehicle_type,id}]", "make (slug) [+ year] -> that make's models", "make + model (slugs) -> the model's years, each with its id", "q (+ limit, year) -> fuzzy search results; a model hit carries ids {year: id} and id for the year the query named"],
|
|
987
|
+
returns: "{data:{years}} | {data:{year,makes}} | {data:{year,make_id,models}} | {data:{make,models}} | {data:{make,model,years,year_ranges,vehicles:[{year,id}]}} | {data:{query,results:[{kind,make_name,make_slug,model_name,model_slug,vehicle_type,years,year_ranges,match_kind,id?,ids?}]}}",
|
|
988
|
+
sdk: "client.vehicles({ year, makeId }) / client.vehicles({ make, model }) / client.searchVehicles(q, { limit, year })",
|
|
737
989
|
cli: "car-image search porsche 911",
|
|
738
990
|
},
|
|
991
|
+
{
|
|
992
|
+
method: "GET",
|
|
993
|
+
path: "/api/v1/vehicles/{id}",
|
|
994
|
+
summary: "The catalog vehicle a stable id names, with its years and the image URLs that render it.",
|
|
995
|
+
auth: "public",
|
|
996
|
+
credits: "free",
|
|
997
|
+
returns: "{data:{id, make:{id,name,slug}, model:{name,slug,vehicle_type}, year, years, year_ranges, image_path, images:{front,front-3-4,side,side-right,rear,rear-3-4}}, request_id}",
|
|
998
|
+
errors: ["400 not a vehicle id", "404 unknown id"],
|
|
999
|
+
notes: "Ids are a pure function of the catalog spelling and never change; store them instead of make/model/year strings.",
|
|
1000
|
+
sdk: "client.vehicle('veh_395yw8tn73ff8')",
|
|
1001
|
+
cli: "car-image get --vehicle veh_395yw8tn73ff8",
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
method: "GET",
|
|
1005
|
+
path: "/api/v1/vin/{vin}",
|
|
1006
|
+
summary: "Decode a full or partial VIN (NHTSA vPIC) and name the catalog vehicle it maps to.",
|
|
1007
|
+
auth: "bearer",
|
|
1008
|
+
credits: "free",
|
|
1009
|
+
params: ["vin in the path: 17 characters, or a partial VIN of 5+ characters with * for each unknown position (case, spaces and dashes ignored)", "year (optional model-year hint)"],
|
|
1010
|
+
returns: "{data:{vin, valid, errors:[{code,text}], suggested_vin, year, make, model, trim, series, body_class, vehicle_type, doors, drive_type, fuel_type, engine:{cylinders,displacement_l,hp,model}, transmission:{style,speeds}, manufacturer, plant:{city,state,country}, gvwr, attributes:{…every decoded variable}, vehicle:{id,make,model,year,image_path}|null, source:vpic-db|vpic-api}, request_id}",
|
|
1011
|
+
errors: ["400 invalid VIN or year", "401", "404 VIN not recognized", "502 both decoders unavailable"],
|
|
1012
|
+
notes: "vehicle.id is a stable vehicle id: pass it as vehicle= to /api/v1/images/car, /api/v1/image-urls or /api/v1/3d. vehicle is null when the catalog does not carry the car.",
|
|
1013
|
+
sdk: "client.decodeVin('1HGCM82633A004352', { year: 2003 })",
|
|
1014
|
+
cli: "car-image vin 1HGCM82633A004352",
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
path: "/api/v1/3d",
|
|
1019
|
+
summary: "Create a textured 3D model (GLB, USDZ, FBX and a thumbnail) of one vehicle in one paint color.",
|
|
1020
|
+
auth: "bearer",
|
|
1021
|
+
credits: `${MODEL_3D_PRICE} per model at launch (credits_per_model, reported by GET /api/v1/3d), charged at creation whether the model is cached, in progress or new -- free when the account already holds a live request for the same vehicle and color (billing.already_owned); a failed model is refunded; polls, downloads and publishing are free`,
|
|
1022
|
+
params: ["make, model, year — or vehicle (a veh_… id), never both", "color (preset or hex such as #1a2b3c; default silver)", "webhook_url (https, public host; receives one POST {event:'3d_model.ready'|'3d_model.failed', data, sent_at} with X-CarImage-Event and X-CarImage-Delivery, retried up to five times)", "webhook_secret (with webhook_url: X-CarImage-Signature: sha256=<hex HMAC of the raw body>)", "publish (true: key-free URLs and an embed at once, see /api/v1/3d/{id}/publish)", "header Idempotency-Key (optional; same rules as /api/v1/image-urls)"],
|
|
1023
|
+
returns: "202 while queued or processing, 200 when already ready: {data:{id, object:'3d_model', status:queued|processing|ready|failed, progress:0-100, stage, vehicle:{id,make,model,year}, color, generator, files:{glb|glb_web|usdz|fbx|thumbnail:{url,bytes,content_type}}|null, polycount, error, estimated_seconds_remaining, webhook, public:{id,url,files,embed:{script,html},published_at}|null, created_at, updated_at, ready_at}, billing:{charged_on:'creation',credits_charged,credits_remaining,already_owned}, request_id}; Location: /api/v1/3d/{id}",
|
|
1024
|
+
errors: ["400 invalid body or webhook URL", "401", "402 insufficient credits", "404 vehicle not in catalog", "409/422 Idempotency-Key conflicts", "503 3D models disabled or at daily capacity (Retry-After; nothing charged)"],
|
|
1025
|
+
notes: "Ask the human before spending. A model takes one to seven minutes: poll GET /api/v1/3d/{id} every few seconds or use the webhook; never re-POST to check progress. Ordering a vehicle and color the account already owns is free, so re-ordering is safe.",
|
|
1026
|
+
sdk: "client.create3dModel({ make, model, year } | { vehicle }, { color, webhookUrl, webhookSecret, publish, idempotencyKey }) then client.wait3dModel(id)",
|
|
1027
|
+
cli: "car-image 3d create --make Toyota --model Camry --year 2025 --color 1a2b3c --publish --wait --out ./models",
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
method: "POST",
|
|
1031
|
+
path: "/api/v1/3d/{id}/publish",
|
|
1032
|
+
summary: "Publish one of your 3D requests: key-free URLs and a two-line embed, hosted by Car Image. DELETE unpublishes.",
|
|
1033
|
+
auth: "bearer",
|
|
1034
|
+
credits: "free",
|
|
1035
|
+
returns: "{data: 3d_model with public:{id:'m3d_…', url, files:{glb,usdz,poster:{url,bytes,content_type}}|null (null until ready), embed:{script,html}, published_at}, request_id}; DELETE answers the same with public: null",
|
|
1036
|
+
errors: ["404 unknown id (ids are scoped to the account)", "409 the model failed (nothing to publish)"],
|
|
1037
|
+
notes: "Paste public.embed.html into any page: <script src=…/embed/3d.js async></script> and <car-3d model=\"m3d_…\"></car-3d>. No key in the page; the files are served from the CDN. Unpublished links stop working within the hour.",
|
|
1038
|
+
sdk: "client.publish3dModel(id) / client.unpublish3dModel(id)",
|
|
1039
|
+
cli: "car-image 3d publish <id> [--unpublish]",
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
method: "GET",
|
|
1043
|
+
path: "/api/v1/3d/public/{public_id}",
|
|
1044
|
+
summary: "A published model for anyone holding its public id: status, vehicle name, color, key-free file URLs and embed snippets.",
|
|
1045
|
+
auth: "public",
|
|
1046
|
+
credits: "free",
|
|
1047
|
+
returns: "{data:{id, object:'3d_model_public', status, progress, estimated_seconds_remaining, vehicle:{id,make,model,year,name}, color, color_hex, alt, files:{glb,usdz,poster}|null, embed:{script,html,model_viewer}, published_at, updated_at}, request_id}. /model.glb, /model.usdz and /poster.png under the same path answer 302 to the CDN copy.",
|
|
1048
|
+
errors: ["404 unknown or unpublished id", "409 a file asked for before the model is ready (Retry-After)"],
|
|
1049
|
+
notes: "CORS *; cacheable. This is what the embed script reads; nothing here names the account.",
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
method: "GET",
|
|
1053
|
+
path: "/api/v1/3d",
|
|
1054
|
+
summary: "The caller's recent 3D requests, newest first, with the current price per model.",
|
|
1055
|
+
auth: "bearer",
|
|
1056
|
+
credits: "free",
|
|
1057
|
+
params: ["limit (1-50; default 20)"],
|
|
1058
|
+
returns: "{data:[3d_model…], pricing:{credits_per_model}, request_id}",
|
|
1059
|
+
sdk: "client.list3dModels({ limit: 20 })",
|
|
1060
|
+
cli: "car-image 3d list",
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
method: "GET",
|
|
1064
|
+
path: "/api/v1/3d/{id}",
|
|
1065
|
+
summary: "Status, progress and files of one 3D request; a processing model is re-checked with the provider.",
|
|
1066
|
+
auth: "bearer",
|
|
1067
|
+
credits: "free",
|
|
1068
|
+
returns: "{data: 3d_model, request_id}",
|
|
1069
|
+
errors: ["404 unknown id (ids are scoped to the account)"],
|
|
1070
|
+
sdk: "client.get3dModel(id) / client.wait3dModel(id, { intervalMs, timeoutMs, onProgress })",
|
|
1071
|
+
cli: "car-image 3d get <id> --wait",
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
method: "GET",
|
|
1075
|
+
path: "/api/v1/3d/{id}/files/{kind}",
|
|
1076
|
+
summary: `One file of a ready model (kind: ${MODEL_3D_FILE_KINDS.join("|")}): a 302 to a signed URL valid for an hour.`,
|
|
1077
|
+
auth: "bearer",
|
|
1078
|
+
credits: "free",
|
|
1079
|
+
returns: "302 Location: <signed storage URL>; X-Content-Length, X-File-Content-Type",
|
|
1080
|
+
errors: ["404 unknown id or kind", "409 model not ready yet (Retry-After: 15)"],
|
|
1081
|
+
notes: "Follow the redirect without the Authorization header; the signed URL needs no key. files.<kind>.url on the model JSON is this endpoint.",
|
|
1082
|
+
sdk: "client.download3dModel(id, 'glb') // { bytes, contentType }",
|
|
1083
|
+
cli: "car-image 3d download <id> --format glb --out model.glb",
|
|
1084
|
+
},
|
|
739
1085
|
{
|
|
740
1086
|
method: "POST",
|
|
741
1087
|
path: "/api/v1/feedback",
|
|
742
1088
|
summary: "Rate a delivered image.",
|
|
743
1089
|
auth: "bearer",
|
|
744
1090
|
credits: "free",
|
|
745
|
-
params: ["request_id OR make, model, year (+ view, color)", "rating 1-5 and/or verdict good|bad", "reason (optional)"],
|
|
1091
|
+
params: ["request_id OR make, model, year (+ view, color as a preset or hex)", "rating 1-5 and/or verdict good|bad", "reason (optional)"],
|
|
746
1092
|
returns: "201 {data:{...}, request_id}",
|
|
747
1093
|
sdk: "client.feedback({ requestId, verdict: 'good' })",
|
|
748
1094
|
cli: "car-image feedback --request-id <id> --good",
|
|
@@ -907,7 +1253,8 @@ export function formatApiReference(filter) {
|
|
|
907
1253
|
const header = [
|
|
908
1254
|
`Car Image API — base URL ${DEFAULT_BASE_URL}`,
|
|
909
1255
|
`Auth: Authorization: Bearer cimg_… (never in URLs). Errors: application/problem+json {type,title,status,detail,request_id}.`,
|
|
910
|
-
`Pricing: ${CREDITS_PER_IMAGE} credit per delivered image, $1 = ${CREDITS_PER_DOLLAR} credits, ${FREE_CREDITS} free credits, no subscription.`,
|
|
1256
|
+
`Pricing: ${CREDITS_PER_IMAGE} credit per delivered image, $1 = ${CREDITS_PER_DOLLAR} credits, ${FREE_CREDITS} free credits, no subscription; a 3D model is ${MODEL_3D_PRICE}.`,
|
|
1257
|
+
`Vehicles: make + model + year, or vehicle=<stable id such as veh_395yw8tn73ff8> (from /api/v1/vehicles, /api/v1/vin or resolve), never both. Colors: ${COLORS.length} presets or any hex paint (URLs: color=1a2b3c bare; JSON: "#1a2b3c" or "1a2b3c"; responses echo #1a2b3c).`,
|
|
911
1258
|
"",
|
|
912
1259
|
];
|
|
913
1260
|
if (endpoints.length === 0) {
|
|
@@ -954,9 +1301,13 @@ export function nextStepFor(status) {
|
|
|
954
1301
|
case 403:
|
|
955
1302
|
return "This key lacks the required scope. Ask the human to create a key with the right scopes in the dashboard.";
|
|
956
1303
|
case 404:
|
|
957
|
-
return "The vehicle is not in the catalog. Use search_vehicles or
|
|
1304
|
+
return "The vehicle is not in the catalog (or the id is unknown). Use search_vehicles, resolve_vehicle or decode_vin to find the canonical make/model/year or its vehicle id.";
|
|
1305
|
+
case 409:
|
|
1306
|
+
return "Not ready or still in progress (a 3D model still being made, or an Idempotency-Key request still running). Wait the retry_after seconds, then poll again; do not create a second one.";
|
|
958
1307
|
case 429:
|
|
959
1308
|
return "Rate limited. Wait the retry_after seconds before retrying; do not hammer the API.";
|
|
1309
|
+
case 503:
|
|
1310
|
+
return "Temporarily unavailable (a feature is switched off or at its daily capacity); nothing was charged. Wait the retry_after seconds before trying again, and tell the human if it persists.";
|
|
960
1311
|
default:
|
|
961
1312
|
return "Retry once if this looks transient; otherwise report the detail and request_id to the human.";
|
|
962
1313
|
}
|