The ref grammar
Hold the platform's identifier language offline: @cardog/entities validates, builds, and types refs with zero API calls — resolution is where the API begins.
Every identifier in the Vehicle Graph — make:honda,
model-year:honda/civic/2021, squish:5TDGSKFCR — obeys one grammar, and
that grammar is published as an open-source package. Install it and your code
can validate, build, parse, and type refs entirely offline: no API key, no
network call, no rate limit. The language is free to hold. The answers are
what the API serves.
npm install @cardog/entitiesESM, TypeScript-first, zero dependencies, Apache-2.0. Runs in Node, browsers, workers, and edge runtimes.
Validate and build, offline
import {
isRef, isRefOf, buildRef, parseRef,
modelYearRef, parseModelYearRef, recallRef,
} from "@cardog/entities";
isRef("make:tesla"); // true
isRef("make:Tesla"); // false — casing is part of the grammar
isRefOf(input, "model-year"); // narrows to EntityRef<"model-year">
modelYearRef("model:tesla/model-y", 2024); // "model-year:tesla/model-y/2024"
parseModelYearRef("model-year:honda/civic/2021");
// { make: "make:honda", model: "model:honda/civic", year: 2021 }
recallRef("nhtsa", "23V123"); // "recall:nhtsa/23v123"Every builder validates against the grammar and throws rather than normalizes — the same never-fuzzy law the API enforces at its boundary, available in your process. Validate user input before it ever costs a call; reject a malformed ref in a form handler; assert grammar in your tests.
The VIN grains, offline
nano and squish — the grains between one VIN and a whole model year —
are pure functions of the VIN, so the package derives them locally:
import { squishFromVin, nanoFromVin, squishRef, nanoRef } from "@cardog/entities";
squishFromVin("5TDGSKFC8RS123456"); // "5TDGSKFCR" — the exact-config market grain
nanoFromVin("5TDGSKFC8RS123456"); // "5TDGSKFC*RS" — the comparables grain
squishRef("5TDGSKFCR"); // "squish:5TDGSKFCR" — ready for /v2/quotesA VIN in your database becomes a market-grain ref without touching the network — the API call you then make is the one that quotes it, not the one that derives it.
Typed unions
The enumerable domains ship as TypeScript unions, so a misspelled ref in a fixed vocabulary fails at compile time, not at request time:
import type { FuelTypeRef, BodyStyleRef, EntityDomain } from "@cardog/entities/types";
import type { SpecAttributeId } from "@cardog/entities";
const fuel: FuelTypeRef = "fuel-type:electric"; // ✓ checked by tsc
const attr: SpecAttributeId = "curbWeight"; // the 155-attribute catalog, typedThe open-ended domains (make, model) are typed structurally
(EntityRef<"make">) — their vocabularies live in the registry, and free
text meets them through resolve.
The package also carries the spec attribute catalog itself
(@cardog/entities/spec) — the same catalog GET /v2/specs/catalog
serves, importable for building filter UIs and validating
spec.{attributeId} query keys before sending them.
The stability contract
Refs are permanent join keys, and the package states that machine-checkably:
GRAMMAR_VERSIONis1— it bumps only for a breaking change to the grammar itself, which the platform treats as a never-event. New domains and entities are additive.vocabularycarries the provenance of the build the package was generated from, so what your process validates against is auditable.- A ref, once issued, keeps its meaning for the life of the platform. Store refs in your columns and your config; they will not rot.
Where the API begins
The package deliberately contains no entity data — no make list, no model vocabulary, no display names — and no VIN decoding. Holding the grammar tells you a ref is well-formed; only the registry can tell you what it names:
# free text → refs (the one door for unstructured input)
curl "https://api.cardog.app/v2/entities/resolve?q=2021%20civic" -H "x-api-key: $CARDOG_API_KEY"
# ref → the entity: names, hierarchy, counts, links into every group
curl "https://api.cardog.app/v2/entities/model:honda%2Fcivic" -H "x-api-key: $CARDOG_API_KEY"
# VIN → identity as refs
curl "https://api.cardog.app/v2/vin/5TDGSKFC8RS123456" -H "x-api-key: $CARDOG_API_KEY"That is the intended division of labour: hold the language locally, ask the platform for answers. Validate and construct refs offline all day; the moment a question needs the registry — resolution, enumeration, specs, recalls, quotes — /v2/entities and its sibling groups are the doors.