The Vehicle Graph
The data model behind every endpoint: typed entities, permanent refs, and the grains that connect a VIN to specs, recalls, and the live market.
Every Cardog endpoint is a view onto one structure: a graph of automotive entities, each with a permanent identifier, each linked to what the platform knows about it. Understand the graph and the API stops being a list of routes — every response tells you where you can go next.
Nodes: entities and their refs
An entity is a node in the graph — a make, a model, a model year, a body
style, a fuel type. Every entity has exactly one ref: a permanent,
human-readable identifier of the form {domain}:{key}.
make:honda
model:honda/civic
model-year:honda/civic/2021
fuel-type:electric
recall:tc/2024-123Refs are all-lowercase, with / separating the segments of composite keys.
They are identifiers, not display strings — make:mini is the ref; "MINI"
is the display name carried in the entity's name field. This distinction
is deliberate: display casing varies across data sources, and case-splits are
how catalogs silently fracture. The ref never varies.
Two domains break the lowercase rule, for a reason: nano: and squish:
keys are uppercase VIN-charset strings, because they are machine-derived from
VINs — no human ever types one from memory, and keeping them byte-recognizable
against VINs is worth the exception.
Refs are stable join keys. Store them in your own database columns, your
config, your agent's memory. make:honda will mean Honda for as long as the
platform exists. This permanence is the contract that makes the graph usable
as infrastructure.
The grammar itself is open source: npm install @cardog/entities and your
code validates, builds, and types refs with zero API calls — see
The ref grammar for holding the language offline.
Edges: hierarchy and links
Entities form a hierarchy: a model year belongs to a model, a model to a make. Dereference any entity and you get its ancestors, its children, and counts of what hangs off it:
curl "https://api.cardog.app/v2/entities/model:honda/civic" \
-H "x-api-key: $CARDOG_API_KEY"The response carries parents (the chain up to make:honda),
children (the model years), counts (how many live listings, recall
campaigns, and children the graph holds for this node), and links — ready
URLs into every other group for this entity. Responses are self-describing:
follow the links instead of constructing paths.
Entering the graph
There are exactly two doors, by design.
Free text enters through resolve — and nowhere else. Your user says
"2021 Civic", not model-year:honda/civic/2021:
curl "https://api.cardog.app/v2/entities/resolve?q=civic&domain=model" \
-H "x-api-key: $CARDOG_API_KEY"Resolve returns candidates with confidence scores, best first. best is
non-null only when a candidate clears the confidence floor — the API never
guesses on your behalf. Everywhere else, a parameter that is not a well-formed,
known ref is a 400 that names the offending value and points you back to
resolve. Never a fuzzy match, never a silently empty result. An unknown ref is
a fact worth telling you about, not a thing to paper over.
VINs enter through decode. A VIN is the physical world's pointer into the graph:
curl "https://api.cardog.app/v2/vin/1HGCM82633A123456" \
-H "x-api-key: $CARDOG_API_KEY"The decode returns the vehicle's identity as refs — its make, model, model year, fuel type, body style — each one verified against the registry before it is claimed. A null ref in the response means "not derivable for this VIN," never "we guessed."
The grains: nano and squish
Between "one specific vehicle" (a VIN) and "a model year" (millions of vehicles) sit two machine-derived grains, and most market questions live there:
squish:— the first 9 meaningful VIN characters (WMI + VDS + model year). Every VIN sharing a squish is the same configuration of the same model year: same plant-agnostic build. This is the exact-config market grain.nano:— the squish plus plant. Vehicles sharing a nano are fungible: same build, same origin. This is the deduplication and comparables grain.
Both are pure functions of the VIN — derivable offline, forever, from the VIN alone. A quote at the squish grain answers "what is this exact car worth on the live market," not "what do Civics go for."
What hangs off the graph
Every dataset the platform serves is keyed to graph nodes, which is what makes the API composable:
| Data | Keyed by | Group |
|---|---|---|
| Live listings, facets, counts | entity refs + nano | Listings |
| Canonical specs (155 attributes) | model-year ref | Specs |
| Recall campaigns (TC + NHTSA, fused) | recall ref → model-year refs | Recalls |
| Live quotes and the tape | model-year ref, squish | Instruments, Quotes, Tape |
| Safety ratings and complaints | model-year ref | Safety |
One ref, held once, dereferences into all of it. That is the shape of a typical integration: resolve once (or decode a VIN), hold the refs, query everything else with them. The refs in your database columns are the same refs in our registry — your data and ours join on identical keys.
Provenance
Facts in the graph carry authority. A recall record is Transport Canada's,
not ours — responses carry the issuing authority and data freshness
(asOf) so what you cite is citable. Where a fact's tier matters
(authoritative, commercial, derived, observed), the response says so rather
than flattening everything into unattributed data.
Why a graph, and not a database dump
The registry's vocabulary is open — the ref grammar is documented, refs are free to hold, and validating one requires no API call. What the platform meters is answers: resolution, enumeration, and the data keyed to the nodes. Browse endpoints are paginated for evaluation, not extraction. The graph is infrastructure you build on, with the vocabulary as the public interface and the facts as the product.