API conventions
The mechanics every v2 group shares: encoding, response shape, null semantics, and the stability contract.
Every /v2 group speaks the same dialect. Learn the conventions once and
every endpoint reads the same way — the reference pages then only have to
tell you what an operation returns, never how to talk to it.
Request encoding
Repeatable filters repeat the parameter. No commas, no bespoke delimiters — a filter that takes many values takes the parameter many times (this example, like every query string on this page, is generated by the contract's own URL codec):
GET /v2/listings/search?make=make:tesla&make=make:rivianRanges are dotted. {field}.min and {field}.max, either side
optional:
GET /v2/listings/search?year.min=2022&price.min=20000&price.max=60000Spec filters address the catalog directly. spec.{attributeId} filters
on any of the canonical spec attributes — numeric attributes take
.min/.max, availability attributes take repeated values:
GET /v2/listings/search?spec.fuelEconomyCombined.min=35&spec.heatedSeatsFront=standardUnknown spec keys are a 400 unknown_spec_attributes naming the offending
keys — the same never-fuzzy law that governs refs.
Refs in paths vs queries
A ref's slashes are part of the key (model-year:honda/cr-v/2026), so
position matters:
- In a URL path, encode slashes as
%2F:https://api.cardog.app/v2/entities/model-year:honda%2Fcr-v%2F2026 - In a query string, pass the ref raw:
?make=make:tesla
Every links block the API emits is already correctly encoded — one more
reason to follow links instead of assembling paths.
Response conventions
- camelCase keys, everywhere. No snake_case anywhere in a response.
- Dates are ISO 8601 strings (
2026-07-23T12:00:00Z, orYYYY-MM-DDfor date-grain fields like tape bars). - Pagination is an object, not headers: paged responses carry a
paginationblock withpage,limit. Page size is capped per endpoint; the cap is in the reference. - Every response carries
links— rel → server-relative path to adjacent resources (entity → quotes, recalls, listings, specs). Traverse them instead of constructing URLs. nullmeans "not derivable" — never "unknown to us silently." A nullfuelTypeon a VIN decode says the registry cannot support that claim for this VIN; it is a stated fact about coverage, not a shrug. The API never substitutes a guess where it cannot verify.- A failed resolution returns nothing, not something. When
GET /v2/vin/{vin}answers"valid": false, every identity field in that response isnulland every ref isnull— including thenano/squishgrains. We do not fall back to adjacent records to fill the gap, because a plausible-looking join key is worse than an empty one: you would store it. - Derived and observed never share a field. A fact we resolved ourselves
and a fact we saw somewhere are different facts, and the payload keeps them
in different places. A VIN identity's
trimis the catalogue trim the VIN's own pattern resolves to, tagged bytrimAuthorityTier; the words a seller wrote on a listing ride inobservedTrimwith their ownauthorityTierandasOf. Price on the first, display the second, and never let a field's value leave you guessing which one you got.
Versioning and stability
The v2 contract is frozen and changes additively: new fields, new endpoints, new enum members, new error codes — never renames, removals, or type changes on what is already shipped.
- Open sets stay open. Error codes (15 well-known today), authority keys, bar sources — dispatch on the members you know and fall back gracefully; new members are not a breaking change.
- Refs are permanent. The ref grammar is versioned
(
GRAMMAR_VERSION = 1, exported by the registry itself), and a ref, once issued, keeps its meaning for the life of the platform. Store refs; they will not rot. - v1 is legacy. It keeps working for existing integrations, but v2 is
where the contract lives — new work should target
/v2exclusively, and the version switcher in these docs marks v1 accordingly.
The machine-readable contract is GET https://api.cardog.app/v2/openapi.json; these docs
(including this page) are generated from it and from the same source modules
the server imports.
Batch semantics
Batch endpoints are all-accepted, per-item-resolved. One malformed item never fails the batch — it fails its own row with the standard error envelope, in the same position it was sent:
curl -X POST "https://api.cardog.app/v2/vin/batch" \
-H "x-api-key: $CARDOG_API_KEY" \
-H "content-type: application/json" \
-d '{"vins":["1HGCM82633A123456","NOTAVIN"]}'Example
2 keysresults preserves request order; meta carries the counts. Up to
1,000 VINs per batch, metered per VIN
decoded — failed rows cost nothing (see Credits & limits).