# Cardog changelog

What shipped, newest first. Biweekly. The human page is https://cardog.app/changelog; the full docs are one fetch at https://cardog.app/docs.md.

## EV charging stations

**August 31, 2026**


Two endpoints:

- `GET /v2/charging/stations` — stations near a point, nearest first
  (`lat`/`lng`/`radius_km`, plus `connector`, `min_power_kw`,
  `status`).
- `GET /v2/charging/stations/{id}` — one station by its source id.

### Why this is not the old /charging

v1 proxied a live upstream; this serves the Open Charge Map dataset from
our own database. 105,985 stations (97,869 operational), an `asOf`
timestamp you can cite, and zero dependence on anyone else's uptime.
Fields we cannot vouch for (`photos`, `rating`, `network`) are
dropped from the contract, not nulled.

Requests are metered at 1 credit; the rate card at
[/v2/pricing](https://api.cardog.app/v2/pricing) moved to version 6 to
say so.

Read the [charging guide](/docs/charging) or the
[reference](/docs/reference/charging).


---

## The safety record, in five blocks

**August 21, 2026**


**This is a breaking change**, shipping at `2.0.0-rc.3`. If you read
`overallStars`, `bodyStyle`, `esc` or any other flat field on a safety
rating, it has moved. Nothing is deprecated — it is relocated, and the map
is below.

A rating used to answer in a different language than the decode that found
it. The decode said `make:honda`; the rating said `"HONDA"`. The decode
said `body-style:pickup`; the rating said `"PU/CC"`. Same vehicle, same
response, two vocabularies — and only one of them was something you could
join on. Underneath that, roughly forty flat fields were answering five
different questions with nothing saying which was which.

```jsonc
{
  "makeRef": "make:ram",  "make": "RAM",
  "config":     { /* what this rating COVERS — every axis an ID */ },
  "crash":      { /* what NHTSA MEASURED — with denominators and units */ },
  "equipment":  { /* what the vehicle HAS — one closed answer each */ },
  "published":  { /* what the source ACTUALLY SAID — verbatim */ },
  "provenance": { /* who said it, and which release */ }
}
```

### Where things went

| before | after |
| --- | --- |
| `overallStars: 5` | `crash.overall: { stars: 5, outOf: 5 }` |
| `frontalDriverStars` | `crash.frontal.driver` |
| `rolloverPossibility: 0.095` | `crash.rollover.possibility: { value: 9.5, unitCode: "P1" }` |
| `staticStabilityFactor: 1.48` | `crash.rollover.staticStabilityFactor: { value: 1.48, unitCode: "C62" }` |
| `dynamicTipResult: "No Tip"` | `crash.rollover.dynamicTip: "no-tip"` |
| `bodyStyle`, `driveTrain`, `vehicleType` | `published.*`, plus resolved IDs in `config` |
| `esc`, `abs`, `backupCamera`, … | `equipment.*`, plus the raw in `published` |
| `productionRelease`, `source` | `provenance.*` |

### Every axis is an ID your decode already speaks

```bash
curl -H "Authorization: Bearer $CARDOG_KEY" \
  "https://api.cardog.app/v2/safety/nano:1C6SRFVTTN"
# → "config": { "bodyStyleRef": "body-style:pickup",
#               "bodyCabRef":   "body-cab:crew-super-crew-crew-max",
#               "driveTypeRefs": ["drive-type:4wd-4-wheel-drive-4x4"] }
```

The cab is the interesting one. NHTSA rates a crew cab and a quad cab
separately — on a 2026 Ram 1500 that is four stars against five — and it
publishes the distinction buried inside a body code (`PU/CC` vs
`PU/EC`) that nothing could compare against. It is now its own axis.

### Every safety feature, one answer

The equipment fields were free text, and the source writes them **eighty-nine
different ways**: three capitalisations of "Standard", five spellings of
nothing, take rates baked into the answer (`optional (72%)`), per-trim
breakdowns, and — in the anti-lock brakes column — a manufacturer's
collision-braking trim notes.

Now: `standard`, `optional`, `varies`, `unavailable`, `not-published`.

**`not-published` is not `unavailable`.** One means the source left the
field blank; the other means the vehicle does not have the feature. A 2026
Civic has stability control — required by law since 2012 — and an empty
column. Those are different answers and you now get the right one.

`sourceClaim` tells you who answered, and `grain` whether it describes the
whole rated configuration or one specific trim. Two sources answer these and
they are not always answering the same question.

### And numbers say what they are

`0.095` for a rollover probability could be 9.5% or 0.095% — the response
never said. Measures now ship as `{ value, unitCode }` using UN/CEFACT
Recommendation 20 codes; see [Units](/docs/units). `stars: null` means
**not rated**, which a bare integer could not distinguish from a rating of
zero.

Grouping also makes one thing legible that the flat shape hid: cars before
2011 carry frontal and side stars and no overall score, because NHTSA did
not publish an overall score until the MY2011 programme redesign. Flat, that
read as missing data.

### Checking our work

`published` carries the source's own text, unedited, beside everything we
resolved. A resolution nobody can check is just an assertion — this is how
you check ours, and how you tell us when we are wrong.

Full detail in the [Safety guide](/docs/safety).


---

## The build code

**August 13, 2026**


Trim names can't identify a vehicle. "Work Truck" spans four weight classes;
"LE" spans gas and hybrid. The VIN's own build pattern — positions 1–8 plus
10, the **nano** — separates them, and as of today it is a first-class key
across the platform.

```bash
curl -H "Authorization: Bearer $CARDOG_KEY" \
  "https://api.cardog.app/v2/vin/2T3RWRFV4MW112492"
# → "nano": "nano:2T3RWRFVMW",
#   "links": { "specsBuild":  "/v2/specs/nano:2T3RWRFVMW",
#              "safetyBuild": "/v2/safety/nano:2T3RWRFVMW",
#              "nanoEntity":  "/v2/entities/nano:2T3RWRFVMW", ... }
```

The nano is pure string math over the VIN — derivable offline with
`@cardog/entities`, stable forever, and now dereferenceable: 824,000 build
codes are registry entities, one per configuration the decoder has verified.

**`GET /v2/specs/nano:{code}` — the config-exact sheet.** The decoded
configuration (weight class, drive, fuel/hybrid family, engine — each with
source, confidence, and the decoder build that produced it) plus every
catalog entry that maps to this build. When several map (option packages
don't change the VIN pattern), you get them all and the fold of what they
agree on — never a silently-picked one.

**`GET /v2/safety/nano:{code}` — the rating for THAT build.** Ratings are
selected by joining the build's decoded axes against what NHTSA declared —
never by matching trim text. The response states its own narrowness:

- `configuration` — exactly one rating survives: it's yours. `tested:
  true` marks the rating whose crash car was literally this build.
- `partial` / `model-year` — the honest short list, or the full set.
  Blockers are named, never papered over.
- `exempt` — NCAP does not rate vehicles over 8,500 lb GVWR. A 2023 F-350
  answers *exempt*, not "missing" — with the listed configurations and
  their driver-assist data still included.

**Weight class, filled in.** North-American catalog rows never carried GVWR
from source sheets; it is now derived from real decoded VINs,
unanimity-gated — 15,000 rows including every 2023 F-350 — so
`/v2/specs/{model-year}` serves the class your pricing model reads, and it
agrees with the decode.

Nothing breaks: model-year refs keep answering everywhere they did, and the
new links ride alongside the old ones. The build code is additive — a
sharper key for consumers who need configuration truth, which is anyone
pricing, insuring, or rating an actual vehicle rather than a nameplate.


---

## API 2.0

**July 30, 2026**

The Cardog API is now one keyed surface at `api.cardog.app/v2`. Resolve
free text to a permanent ref once, then dereference that ref into identity,
specs, listings, live market data, and recalls behind a single key.

```bash
curl -H "Authorization: Bearer $CARDOG_KEY" \
  "https://api.cardog.app/v2/entities/resolve?q=tesla+model+y"
# → { "ref": "model:tesla/model-y", ... }

curl -H "Authorization: Bearer $CARDOG_KEY" \
  "https://api.cardog.app/v2/listings?make=make:tesla&year=2024"
```

`model:tesla/model-y` is a permanent join key. Store it in a database
column, a config file, or an agent's memory file; it will mean the same
vehicle for as long as the platform exists.

**Every non-2xx response carries the fix.** The envelope is
`{ code, message, hint, docs_url, suggestions }`. An unknown ref returns a
400 that names the offending ref and offers the nearest valid ones — we
never silently fuzzy-match a typo into a different vehicle. A wrong answer
that looks right is worse than an error, so you get the error and the
correction in the same response.

**The contract generates the surface.** Schemas in `@cardog/contracts`
validate responses, not just requests, and every downstream artifact
regenerates from them: the OpenAPI spec, the docs reference, the conformance
table, the live-check suite, both SDKs' types, and the MCP tool layer.
Nothing user-facing is hand-maintained, so nothing user-facing can drift.

**Nine route groups**, all ref-native:

| Group | What it answers |
|---|---|
| `entities` | free text → permanent refs; the only door free text enters |
| `vin` | VIN → identity, with per-item batch |
| `specs` | 155-attribute catalog by trim |
| `listings` | ref-native search, facets, count over live Canadian listings |
| `instruments` · `quotes` · `tape` | live Canadian market data |
| `recalls` | Transport Canada + NHTSA, fused, ref-keyed |
| `pricing` | machine-readable rate card at `GET /v2/pricing` |

**Docs are generated, and readable by machines.**
[cardog.app/docs](https://cardog.app/docs) has zero hand-written endpoint
prose — cURL / TypeScript / Python tabs come from both SDK method maps, ⌘K
search, and a try-it panel that runs against your own key. Every page has a
`.md` twin, and [cardog.app/docs.md](https://cardog.app/docs.md) is the
whole platform in one fetch.

**`@cardog/entities` is on npm, Apache-2.0.** The ref grammar as an open
package: validate, build, and type refs offline, forever, with zero API
calls and zero dependencies. The grammar is free; resolution and the facts
behind the refs are the product.

```ts
import { modelYearRef, parseRef, squishFromVin } from "@cardog/entities";

modelYearRef("model:tesla/model-y", 2024); // "model-year:tesla/model-y/2024"
squishFromVin("5TDGSKFC8RS123456");        // "5TDGSKFCR" — offline, no network
```

**SDKs and MCP.** `@cardog/api` (TypeScript) and `cardog` (PyPI) are
siblings generated from the same OpenAPI artifact, with envelope-typed
errors. `mcp.cardog.io` exposes five job-shaped tools —
`resolve_entity`, `identify_vehicle`, `search_inventory`,
`market_quote`, `check_recalls` — whose descriptions teach the ref
grammar inline.

**Credits are visible on every call.** `X-Credits-Rate`,
`X-Credits-Remaining`, `X-Credits-Allowance`, and `X-Credits-Reset` on
every keyed response; `X-Credits-Source` on every debited one. The rate
card is machine-readable at `GET /v2/pricing`. The free tier stops at 50
credits with an `insufficient_credits` envelope that says how to continue.
Paid tiers bill overage and are never blocked.

### What changed for existing users

- **v1 still works.** No v1 route has been removed and none is being removed
  today. A deprecation window will be announced with dates in a future
  entry, not inferred from this one.
- **Your existing key works on `/v2` unchanged.** One channel gate covers
  API key, session, and internal callers.
- **Free text now enters at one door.** v1 accepted make/model strings on
  several endpoints. In v2, free text goes to `GET /v2/entities/resolve`
  and every other route takes refs. If you send a string where a ref
  belongs, you get a 400 naming the field and pointing at resolve — the
  migration instruction arrives in the response.
- **Errors changed shape.** v1 error bodies were not uniform. v2 is always
  `{ code, message, hint, docs_url, suggestions }`. Dispatch on `code`;
  it is an open set, so fall back to `message` for codes you do not know.
- **Nulls are honest.** `null` means not-derivable from the evidence. It
  never means unknown, and it is never a placeholder.

### Improvements

- Docs content negotiation: `Accept: text/markdown` on any docs page, or
  append `.md`.
- Conformance table and live-check suite generated from the contract and run
  per-PR against a preview deployment.

### Next

Named because they are being built, not because they are available: resolve
that survives messier real-world queries, agent-minted trial keys, recall
watches, `?include=` so one call answers one job, a self-serve portal for
metering and upgrades, and idempotency keys.

*Full reference: [cardog.app/docs](https://cardog.app/docs) · one fetch:
[cardog.app/docs.md](https://cardog.app/docs.md) · this changelog as
markdown: [cardog.app/changelog.md](https://cardog.app/changelog.md)*


---

Previous: [Platform](https://cardog.app/docs/reference/platform.md) — API reference
