npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meterapp/car-image-sdk

v1.10.0

Published

TypeScript SDK for the Car Image API by Meter: studio-quality, transparent-background renders of any vehicle in the open @meterapp/vehicle-db catalog in any paint color, VIN decoding, stable vehicle ids and textured 3D models. Zero runtime dependencies; w

Readme

@meterapp/car-image-sdk

TypeScript SDK for the Car Image API by Meter — studio-quality, transparent-background renders of any vehicle in the open @meterapp/vehicle-db catalog (1,607 makes, 46,120 models, model years 1990–2027), in any paint color; plus stable vehicle ids, free VIN decoding and textured 3D models.

  • Zero runtime dependencies. Works in Node 20+, Vercel/Cloudflare edge runtimes, Deno, Bun and browsers.
  • Typed parameters and responses for every endpoint.
  • CarImageError carries status, title, detail, requestId and the raw RFC 9457 problem.
  • Automatic retries with full jitter on 429/503 (honoring Retry-After); never retries 402.
  • Signed-URL and 3D-model creation carry an Idempotency-Key, so a retried POST replays instead of billing twice.
  • Shared MCP tool definitions (@meterapp/car-image-sdk/mcp) used by both the stdio and the remote MCP servers.
npm install @meterapp/car-image-sdk

Pricing in one line

Every delivered image costs 1 credit, cold or CDN hit. A plan carries the license and a monthly credit allowance: Free (100 credits once at signup, no card; evaluation and personal projects), Pro ($29/month, 25,000 credits a month, commercial license while active), Business ($99/month, 150,000 credits a month); beyond the allowance every paid plan pays $1 per 100 credits, and purchased credits never expire. Signed delivery URLs cost 1 credit when created; loading them is free. A 3D model costs 100 credits ($1.00) once per vehicle and color; VIN decoding, catalog lookups, polling and downloads are free. client.account() returns the plan in force as data.plan; client.options() lists every plan under pricing.plans.

Get a key at https://carimage.dev/dashboard?ref=npm-sdk or run npx @meterapp/car-image login.

Quickstart (Node)

import { CarImageClient } from "@meterapp/car-image-sdk";
import { writeFile } from "node:fs/promises";

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

const image = await client.getImage({
  make: "Porsche",
  model: "911",
  year: 2024,
  view: "front-3-4",  // front | front-3-4 | side | rear-3-4 | rear | rear-3-4-right | side-right | front-3-4-right
  color: "red",       // 15 presets (white black gray silver blue red green brown beige tan orange yellow gold burgundy purple) or any hex: "#1a2b3c"
  size: "medium",     // thumb=256 small=512 medium=768 large=1024 — or width/height, 1–1024 px each
  format: "png",      // png | webp | jpg | auto (auto negotiates WebP or PNG from the Accept header)
});

await writeFile("porsche-911.png", image.bytes);
console.log(image.source, image.creditsCharged, image.creditsRemaining, image.requestId);

image.vehicleId is the stable id of the catalog vehicle served (the X-Vehicle-Id header), so a bytes response still names the car it rendered.

apiKey and baseUrl fall back to CAR_IMAGE_API_KEY / CAR_IMAGE_API_URL when running on a server.

Any paint color

color is one of the fifteen presets or any hex paint — #1a2b3c, 1a2b3c, #abc or abc. Every image call takes it (getImage, getImageUrl, createImageUrls, feedback) and so does create3dModel. The SDK spells a hex bare in query strings (color=1a2b3c) and sends it as written in JSON bodies; responses echo it as #1a2b3c. A hex that matches a preset's own swatch is that preset. The Color type keeps the preset names in autocomplete while accepting any string; isColor(value) tells the two apart from anything else, and COLORS is still the list of presets.

await client.getImage({ make: "Porsche", model: "911", year: 2024, color: "#1a2b3c" });

Stable vehicle ids

Every catalog vehicle (one make, model and year) has a stable id such as veh_395yw8tn73ff8. It never changes across catalog releases, so it is what to store instead of three strings. Every catalog response carries them (searchVehicles hits get id for the year the query named and ids for every year; vehicles({ year, makeId }) models get id; resolve() params get vehicle_id; decodeVin() names one), and every image or 3D call accepts { vehicle } in place of { make, model, year }:

const { data } = await client.searchVehicles("2024 porsche 911");
const id = data.results[0].id;                             // "veh_395yw8tn73ff8"
await client.getImage({ vehicle: id, view: "side" });      // same image as make/model/year
await client.vehicle(id);                                  // { make, model, year, years, image_path, images } — public, no key

ImageParams is { make, model, year } | { vehicle } plus the options, so TypeScript refuses both at once; at runtime whichever you pass is sent and the server answers 400 to both. Responses echo the id as vehicle.vehicle_id (null when the catalog cannot name one).

VIN decoding

decodeVin(vin, { year? }) decodes a full 17-character VIN, or a partial one of at least 5 characters with * for unknown positions, with NHTSA vPIC data — free, key required. data.vehicle is the catalog vehicle it maps to, with the id that renders it, or null when the catalog does not carry the car.

const { data } = await client.decodeVin("1HGCM82633A004352");
data.year, data.make, data.model, data.trim;   // 2003, "Honda", "Accord", "EX-V6"
data.engine, data.transmission, data.plant;    // typed sub-objects; unknown values are null
data.attributes;                               // every decoded vPIC variable, verbatim
if (data.vehicle) await client.getImage({ vehicle: data.vehicle.id });

valid, errors and suggested_vin carry the decoder's diagnostics (a bad check digit, a model-year mismatch with the year hint). A VIN NHTSA has no record of is a 404; when neither decoder answers it is a 502.

3D models

create3dModel builds a textured 3D model — GLB, USDZ, FBX, a PNG thumbnail and glb_web, a browser build a tenth the size — of any catalog vehicle in any paint color, from the same renders the images use. It costs 100 credits at creation whether the model is cached, in progress or new, and nothing for a vehicle and color your account already owns (billing.already_owned; a failed model is refunded); polling, downloads and publishing are free. A model takes one to seven minutes.

const { data: model, billing } = await client.create3dModel(
  { make: "Toyota", model: "Camry", year: 2025 },        // or { vehicle: "veh_…" }
  { color: "#1a2b3c", webhookUrl: "https://example.com/hooks/3d", webhookSecret: process.env.HOOK_SECRET, publish: true }
);
model.status;                                            // "queued" | "processing" | "ready" (cached) | "failed"
billing.already_owned;                                   // true (and credits_charged 0) when you already owned this vehicle in this color
model.public?.embed.html;                                // '<script src="https://carimage.dev/embed/3d.js" async></script>\n<car-3d model="m3d_…"></car-3d>'

const ready = await client.wait3dModel(model.id, {       // polls GET /api/v1/3d/{id}
  intervalMs: 5_000,                                     // default 5 s
  timeoutMs: 600_000,                                    // default 10 min; throws a CarImageError with status 408
  onProgress: (state) => console.log(state.status, state.progress, state.stage, state.estimated_seconds_remaining),
});
if (ready.status === "ready") {
  const glb = await client.download3dModel(ready.id, "glb"); // { bytes, contentType } — also "usdz" | "fbx" | "thumbnail"
  await writeFile("camry.glb", glb.bytes);
}

await client.get3dModel(model.id);                       // one poll
await client.list3dModels({ limit: 20 });                // your recent requests + pricing.credits_per_model
await client.publish3dModel(model.id);                   // data.public: key-free URLs (glb, usdz, poster) and the embed; works before ready
await client.unpublish3dModel(model.id);                 // the links stop working within the hour

ready.files.<kind>.url are API URLs that answer 302 to a signed download valid for an hour; download3dModel follows that redirect itself and never sends the API key to the storage host. A published model's data.public.files need no key at all: model.glb (the browser build), model.usdz and poster.png redirect to immutable copies on the CDN, and data.public.embed.html is a two-line <car-3d> embed that shows the poster at once, loads Google's <model-viewer> when it scrolls into view and takes view, spin, backdrop, ar, static, no-zoom and alt (fetch only attaches it to the client's own origin). With webhookUrl (https, public host) the server POSTs { event: "3d_model.ready" | "3d_model.failed", data, sent_at } once the model settles, signed as X-CarImage-Signature: sha256=<hex HMAC of the raw body> when webhookSecret is set. create3dModel sends an Idempotency-Key like createImageUrls (pass idempotencyKey to make retries across processes safe). A 503 means 3D models are switched off or at the day's capacity; nothing is charged and retryAfterSeconds says when to try again.

Sizing, backgrounds and trimming

Eight views, any paint color, PNG/WebP/JPG up to 1024 px — delivered at whatever box your layout needs.

await client.getImage({
  make: "BMW", model: "M3", year: 2022, view: "side",
  width: 600, height: 400,  // exactly 600×400 (it used to come back square)
  fit: "contain",           // contain | cover | inside; default contain
  trim: true,               // crop to the car's own bounds first, so it fills the box
  padding: 8,               // 0–50 % of the trimmed car's longer side; only with trim
  background: "#f4f4f4",    // transparent (default) | white | black | rrggbb | #rrggbb | rgb | #rgb
  format: "webp",
});

| Parameter | Values | What it does | | --- | --- | --- | | color | one of COLORS · hex as #rrggbb, rrggbb, #rgb or rgb | The paint. Defaults to silver; echoed back as the preset name or #rrggbb. | | size | thumb 256 · small 512 · medium 768 · large 1024 | Square presets; shorthand for width. | | width, height | 1–1024 px each | One dimension keeps the aspect ratio. Both together give exactly width×height (see fit). Nothing is upscaled past 1024. | | fit | contain (default) · cover · inside | Only matters with both dimensions. contain returns exactly the box with the whole car and transparent (or background) padding; cover fills the box and centre-crops; inside keeps the car within the box and may return a smaller image — the old behaviour. | | background | transparent (default) · white · black · hex as rrggbb, #rrggbb, rgb or #rgb | A solid background flattens PNG and WebP too. jpg cannot be transparent and defaults to white. | | trim | true | Crops to the car's alpha bounds before sizing so it fills the box instead of floating in the square source frame. Recommended for non-square layouts. | | padding | 0–50 | Margin around a trimmed car, as a percentage of its longer side. Only with trim. | | format | png (default) · webp · jpg · auto | auto picks WebP for clients whose Accept header lists it and PNG otherwise (Vary: Accept); a signed URL created with auto negotiates on every load. |

The same parameters apply to getImageUrl and to every entry of createImageUrls; the returned vehicle echoes vehicle_id, color, fit, background, trim and padding back. assertImageParams (and therefore every call) rejects a padding without trim, dimensions outside 1–1024, a color that is neither a preset nor a hex, and a request that names neither a vehicle id nor all of make, model and year, before anything is sent.

Next.js route handler

Keep the key on the server; hand the browser signed URLs.

// app/api/car-image/route.ts
import { CarImageClient, CarImageError } from "@meterapp/car-image-sdk";

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

export async function POST(request: Request) {
  const { make, model, year } = await request.json();
  try {
    const { data } = await client.createImageUrls([{ make, model, year, view: "side", width: 768, height: 432, trim: true }], {
      ttlSeconds: 24 * 60 * 60, // 1 minute to 7 days
      maxUses: 0,               // 0 = unlimited redemptions until expiry
    });
    return Response.json({ url: data[0].url, expiresAt: data[0].expires_at });
  } catch (error) {
    if (error instanceof CarImageError) {
      return Response.json({ error: error.detail, requestId: error.requestId }, { status: error.status });
    }
    throw error;
  }
}

Browser

Never ship the API key to a browser. Create signed URLs server-side (above) and use them like any image:

<img src="https://carimage.dev/api/v1/delivery/…" alt="2024 Porsche 911, side view" width="768" height="432" />

The SDK itself is browser-safe (no Node APIs), which is useful for public endpoints such as options(), vehicles() and searchVehicles() that need no key.

Idempotent URL creation

POST /api/v1/image-urls accepts an Idempotency-Key header (1–255 characters of A–Z a–z 0–9 . _ : -). The same key with the same body within 24 hours replays the first response, marked Idempotent-Replayed: true, instead of minting and billing a second batch. The same key with a different body is answered 422; a retry that arrives while the first request is still running gets 409 with Retry-After.

The SDK sends a generated key (sdk_<uuid>) on every createImageUrls call, so its own retries — including a POST whose connection dropped — are always safe. Pass your own to make retries across processes safe too:

await client.createImageUrls(images, { ttlSeconds: 604_800, idempotencyKey: `order-${order.id}` });

Everything else

await client.getImageUrl(params);                       // 1 credit, returns a signed URL instead of bytes
await client.options();                                 // views, colors (and how hex paint is spelled), sizes, fits, backgrounds, trim limits, formats, pricing, catalog coverage, vehicle ids
await client.resolve("red 2024 porsche 911 side view"); // free text -> { vehicle_id, make, model, year, view, color } + candidates + confidence high|medium|low
await client.vehicles();                                // years
await client.vehicles({ year: 2024 });                  // makes for a year
await client.vehicles({ year: 2024, makeId: 7 });       // models for a year + make, each with its vehicle id
await client.vehicles({ make: "porsche", model: "911" }); // a model's years, each with its vehicle id
await client.vehicle("veh_395yw8tn73ff8");              // one vehicle by stable id (public)
await client.searchVehicles("porshe 911", { limit: 5 }); // fuzzy catalog search; model hits carry ids
await client.decodeVin("1HGCM82633A004352");            // free VIN decode + the catalog vehicle it maps to
await client.create3dModel({ vehicle: id }, { color });  // 100 credits; then wait3dModel, get3dModel, list3dModels, download3dModel
await client.feedback({ requestId, verdict: "good" });  // or { make, model, year, rating: 4, reason } or { vehicle, verdict }
await client.listRequests({ sort: "top" });              // vehicle and feature requests; see "Requests & feedback"
await client.account();                                 // credits, auto-reload, usage_30d, key scopes
await client.createCheckout(500);                      // hosted Stripe Checkout URL for extra credits (scope billing:write)
await client.createPlanCheckout("pro", "month");        // hosted Checkout for a plan, or an in-place switch (data.changed) for an account already on one
await client.billingPortal();                           // Stripe Customer Portal URL
await client.health();                                  // { status: "ok" | "degraded", checks }
await client.openapi();                                 // the OpenAPI 3.1 document
await client.fetch("/api/v1/account");                  // authenticated raw fetch (same-origin only)

createImageUrls(images, options) takes 1–50 images and { ttlSeconds, maxUses, renew, renewDays, idempotencyKey }. With renew: true a URL keeps serving past its TTL at one more credit per opened window of ttlSeconds, until renewDays (1–365, default 365) — for email, CMS pages and documents that outlive a TTL. resolve() returns a confidence of "high", "medium" or "low"; ask the human before spending a credit on low.

Requests & feedback

Missing a vehicle, or want a feature? File it from code — every call here is free. Listing and reading need no key; creating, voting and commenting do.

const { data } = await client.listRequests({ kind: "vehicle", sort: "top" }); // public board; add status, limit, cursor
const { data: request, deduplicated } = await client.createRequest({ kind: "vehicle", make: "Toyota", model: "Sienna", year: 2024 });
// deduplicated === true means an identical open request existed and got your vote instead
await client.createRequest({ kind: "feature", title: "Interior views", body: "Dashboard and seats" });
await client.getRequest(request.id);                    // the request plus its comments
await client.voteRequest(request.id);                   // idempotent; unvoteRequest() removes it
await client.commentOnRequest(request.id, "Also the hybrid trim, please");
await client.listRequestComments(request.id);

The team emails you when a requested vehicle goes live. Two more calls tell us who you are; the answers are private to the Car Image team:

await client.shareBuilding("A dealer inventory site for used minivans");
await client.shareReferral("github", "found it in the vehicle-db README"); // source ∈ REFERRAL_SOURCES

Error handling

All failures throw CarImageError:

import { CarImageError } from "@meterapp/car-image-sdk";

try {
  await client.getImage({ make: "Porsche", model: "911", year: 2024 });
} catch (error) {
  if (error instanceof CarImageError) {
    error.status;           // 400 | 401 | 402 | 404 | 409 | 422 | 429 | 502 | 503 | 408 (wait3dModel timeout) | 0 (network)
    error.title;            // "Insufficient credits"
    error.detail;           // "This request needs 1 credit; balance is 0"
    error.requestId;        // "0d1e…" — quote this when contacting support
    error.problem;          // the raw application/problem+json body (402 includes balance and required_credits, or plan, vehicles_this_month, vehicles_per_month and requested with code "plan_vehicle_limit"; a vehicle 404 includes suggestions: real catalog vehicles with ids to retry with, see /docs/vehicles#missing)
    error.retryAfterSeconds; // set on 429, on a 409 (Idempotency-Key conflict, or a 3D file requested before the model is ready) and on a 503
    if (error.isInsufficientCredits) {
      // Send a human to https://carimage.dev/dashboard?ref=npm-sdk#billing — never buy credits automatically.
    }
  }
}

Retries: 429 and 503 are retried up to maxRetries (default 2) with full-jitter exponential backoff, honoring Retry-After. Network failures are retried for GET and for a POST sent under an Idempotency-Key (which createImageUrls always is); any other POST is never duplicated. Configure with new CarImageClient({ maxRetries, retryBaseDelayMs, maxRetryDelayMs, timeoutMs }) or per call via { retries }.

MCP tool definitions

@meterapp/car-image-sdk/mcp exports the tool names, zod input and output schemas, descriptions, annotations and the server instructions shared by the car-image mcp stdio server and the remote server at https://carimage.dev/api/mcp. It needs zod (an optional peer dependency); the main entry point does not.

import {
  MCP_TOOLS, MCP_TOOL_OUTPUT_SCHEMAS, formatApiReference,
  MCP_TOOLSETS, DEFAULT_MCP_TOOLSET, isMcpToolset, mcpInstructions, type McpToolset,
} from "@meterapp/car-image-sdk/mcp";

Tools come in two sets. MCP_TOOLSETS.core (DEFAULT_MCP_TOOLSET, "core") is the twelve an agent needs to find, render, embed, model and publish a car: get_car_image, create_car_image_urls, search_vehicles, resolve_vehicle, decode_vin, list_image_options, get_account, rate_image, create_3d_model, get_3d_model, publish_3d_model, describe_api. MCP_TOOLSETS.all adds the request board and the two "about you" tools: list_requests, request_vehicle, request_feature, get_request, upvote_request, comment_on_request, share_building, share_referral. The hosted server exposes core at https://carimage.dev/api/mcp and all at https://carimage.dev/api/mcp?toolset=all; the stdio server takes car-image mcp --toolset all. mcpInstructions(toolset) returns the matching server instructions (MCP_INSTRUCTIONS is the all text) and isMcpToolset(value) validates a name from a URL or a flag.

get_car_image, each create_car_image_urls entry, rate_image and create_3d_model name the vehicle by make, model and year or by vehicle (a stable id) and take color as a preset or a hex; the image tools also take fit, background, trim and padding alongside view, size and format (auto included; inlineImageFormat is what both servers deliver an inline image as for it: png), and create_car_image_urls honors renew and renew_days and takes idempotency_key (IDEMPOTENCY_KEY_PATTERN), which the stdio server forwards as the Idempotency-Key header. create_3d_model costs 100 credits, and its description and the server instructions tell the agent to confirm with the human first. Every tool but describe_api, whose result is the reference prose itself, advertises an outputSchema and returns structuredContent that matches it, so a host can hand the model typed data instead of re-parsing the text block. The schemas allow unknown fields: the payloads mirror the REST API and gain fields as it grows. The input bounds are the REST API's: max_uses 0–1000 and a search limit of 1–25.

The one-line summary above a result comes from the same helpers on both servers, built from the payload they share: accountSummary (the balance, the plan and its vehicle cap, and "ask the human" under LOW_BALANCE_CREDITS), resolveSummary (which asks for confirmation before a low-confidence charge), searchSummary, imageUrlsSummary (the charge and until when the URLs serve, a replay included), vinSummary and model3dSummary. So does the step under an error: nextStepFor(status) for every tool, and vinNextStepFor(status) for decode_vin, which sends a refused or unknown VIN back to the human (re-check it, above all the maker code, or send a partial VIN with * and a year) instead of to the catalog.

Types

View, Color (a PresetColor or any hex string), Size, Format, RequestFormat, Fit, VehicleId, VehicleSelector, ImageParams, ImageOptions, ImageResult, CreateImageUrlsOptions, CreateImageUrlsResponse, OptionsResponse, FitOption, ResolveResponse, ResolveConfidence ("high" | "medium" | "low"), VehicleSearchResponse, VehicleResponse, VinResult, VinResponse, Model3d, Model3dFileKind, Model3dStatus, CreateModel3dOptions, CreateModel3dResponse, Model3dListResponse, Model3dFileResult, WaitModel3dOptions, AccountResponse, RequestRecord, RequestComment, CreateRequestInput, ReferralSource, … plus the constant arrays VIEWS, COLORS (the presets), SIZES, FORMATS, REQUEST_FORMATS (the formats plus auto), FITS, MODEL_3D_FILE_KINDS, REQUEST_STATUSES, REFERRAL_SOURCES, the patterns HEX_COLOR_PATTERN and VEHICLE_ID_PATTERN, the helpers isColor, isPresetColor, isHexColor, colorParam and vehicleRequestBody, and the limits MAX_DIMENSION (1024), MAX_PADDING_PERCENT (50) and CREDITS_PER_3D_MODEL (100).

License

MIT © Meter