---
title: "Batch decode (metered per VIN)"
method: POST
path: "/v2/vin/batch"
tags: ["vin"]
---

# Batch decode (metered per VIN)

`POST /v2/vin/batch`

## Request

- Base URL: `https://api.cardog.app`
- URL: `https://api.cardog.app/v2/vin/batch`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

N VINs = N metered units. Per-item outcomes: one bad VIN fails its own row, never the batch.

## Request body

- VinBatchRequest
  - `vins` string[], required: 1 to 1000 VINs. Billed per VIN: N VINs cost N units.

## Response `200`

Per-VIN outcomes, request order

- VinBatchResponse
  - `results` union[], required: One result per VIN, in request order
    - union
      - object
        - `ok` true, required
        - `vin` string, required
        - `identity` object, required
          - `vin` string, required
          - `valid` boolean, required: Whether the VIN decodes to a vehicle. When false, every other field and every ref is null; the API does not fill in an unresolved VIN from similar vehicles.
          - `year` integer, nullable, required
          - `doors` integer, nullable
          - `trim` string, nullable
          - `numberOfWheels` integer, nullable
          - `numberOfAxles` integer, nullable
          - `shippingWeight` integer, nullable
          - `grossWeight` integer, nullable
          - `make` string, nullable, required: Display name. Use `refs.make` for joins.
          - `model` string, nullable, required: Display name. Use `refs.model` for joins.
          - `refs` object, required
            - `make` string, nullable, required: Make entity ref, e.g. "make:tesla"
            - `model` string, nullable, required: Model entity ref, e.g. "model:tesla/model-y"
            - `modelYear` string, nullable, required: Model-year entity ref, e.g. "model-year:tesla/model-y/2024"
            - `bodyStyle` string, nullable, required
            - `fuelType` string, nullable, required
            - `driveType` string, nullable, required
            - `transmission` string, nullable, required
            - `electrificationLevel` string, nullable, required
            - `vehicleType` string, nullable, required
            - `gvwr` string, nullable
            - `country` string, nullable, required: Ref of the country the vehicle was built in, e.g. "country:canada"
            - `bodyCab` string, nullable
            - `bedType` string, nullable
            - `series` string, nullable
            - `fuelTypeSecondary` string, nullable
            - `trailerBodyType` string, nullable
            - `trailerType` string, nullable
          - `nano` string, nullable, required: Build code ref (VIN positions 1–8 plus 10), e.g. "nano:5TDGSKFCRS". Vehicles sharing it are the same build, so use it as the deduplication and comparables key. Null when `valid` is false.
          - `squish` string, nullable, required: Configuration key ref (WMI + VDS + model year, plant-agnostic), e.g. "squish:5TDGSKFCS". This is the level market prices are quoted at. Null when `valid` is false.
          - `authorityTier` 'authoritative' | 'commercial' | 'derived' | 'observed': Trust tier of the identity facts. Source documents are never identified.
          - `reasons` string[], nullable: Why the VIN yielded no identity, from a closed vocabulary of eleven in three classes. The engine's refusals, written beside a row: WMI_UNKNOWN, PATTERN_UNKNOWN, UNKNOWN_MODEL_YEAR, INVALID_LENGTH, INVALID_CHARACTERS, CHECK_DIGIT_MISMATCH, NON_STANDARD_CHECK_DIGIT. The fold's own, for a refusal the engine gives no code for: NO_PATTERN_MATCH, MAKE_REF_GRAMMAR. And two minted at the READ, which answer a different question — not 'why could we not name this build' but 'why is there no row to read': UNSEEN_BUILD (the build is well-formed and has never been decoded — distinct from decoded and refused, and the one the refresh top-up mints next) and UNKEYABLE_VIN (the VIN projects onto no build code, so there is no key). Non-empty whenever `valid` is false; null exactly when `valid` is true. Typed as strings on the wire on purpose: the row classes are closed by a CHECK on ocean.identity.status_reasons and the read class by the API, so a new code is a deliberate change and never breaks a client parsing this field.
          - `corgiBuild` string, nullable: Decoder build that minted this identity, e.g. "2026_08_3befa53".
          - `attributes` object, nullable: Non-entity decode output for this build. Not the spec sheet.
          - `links` object, required: Server-relative paths to related resources. Always includes "instrument", "recalls", "listings", and one entity link per ref.
      - object
        - `ok` false, required
        - `vin` string, required
        - `error` object, required
          - `code` string, required: Error code to branch on. Well-known codes: invalid_request, invalid_filters, invalid_ref, invalid_vin, invalid_nano_refs, version_sunset, unknown_entity_refs, unknown_entity_domain, unknown_spec_attributes, not_found, unauthorized, forbidden, rate_limited, insufficient_credits, internal_error, unavailable. New codes may be added; fall back to message and hint for codes you do not know.
          - `message` string, required: Readable description naming the offending input
          - `hint` string: What to do next, e.g. "Resolve free text at GET /v2/entities/resolve"
          - `docs_url` string: URL of a Markdown page explaining this error code and how to fix it
          - `details` object[]: Field-level validation failures, one per failing path
            - `path` string: The failing field, e.g. "filters.makes.0"
            - `message` string, required
          - `refs` string[]: The offending refs or attribute ids, verbatim. Set for unknown_entity_refs, unknown_spec_attributes, and invalid_nano_refs.
          - `suggestions` object[]: Nearest-ref candidates per offending input. Advisory only; the API does not apply them.
            - `invalid` string, required: The offending input, verbatim
            - `nearest` object[], required: Best candidates, most likely first. Advisory only; the API does not apply them.
              - …
            - `resolve` string: Server-relative URL of the resolve call that turns this input into refs, e.g. "/v2/entities/resolve?domain=make&q=teslla"
          - `links` object: Related resources, each a server-relative path. invalid_vin carries {"feedback": "/v2/feedback"}: if you know what the vehicle is, report it there.
  - `meta` object, required
    - `requested` integer, required
    - `decoded` integer, required
    - `failed` integer, required

## Other responses

- `400`: Invalid request — grammar failure or unknown-but-well-formed refs, NAMED in `refs`/`details`, with nearest-ref `suggestions` where computable. Never a fuzzy fallback.
- `401`: No valid credential (API key, session, or internal token)
- `402`: Insufficient credits (free-tier hard stop; paid tiers bill overage instead)
- `429`: Per-minute rate limit exceeded
- `500`: Internal error

---

[API](https://skmtc.dev/cardog/apis/cardog-api-v2.md) · [All operations](https://skmtc.dev/cardog/apis/cardog-api-v2/llms.txt) · [OpenAPI document](https://skmtc.dev/cardog/apis/cardog-api-v2/revisions/b9d48193126f?raw)
