Skip to main content

Coverage

Cardinal Gray enriches AccountData::Vehicle objects with real-time data from state DMV systems. This guide outlines field availability across all 50 states for both sync_public and sync_private data sources.
Field availability varies based on state DMV system capabilities and data freshness.
Hover over and click any state to view the Vehicle data returned from that state’s DMV.

Integration pattern: POST + ping

Two common ways to run enrichment:
  • Create + enrich in one shot: POST /title (optionally requesting NMVTIS and/or state enrichment immediately)
  • Enrich an existing entry: POST /title/{id}/data

Option A: Directly via /title

You can query nmvtis, public, or private data directly via the POST /title endpoint. This is beneficial when you need to need to initialize an account using just a VIN or cross-validate collected information (e.g., structured originations/deal data or unstructured title/reg documents) against DMV data.
type InitTitleResponse = {
  entryId: string;
  job_status: "enriching" | "complete" | "error";
  nmvtis_pull?: unknown;
  state_pull?: unknown;
  account_data?: { vehicle?: { title?: { issuing_state?: string; odometer_reading?: string } } };
};

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

export async function postTitleAndWait({
  apiBaseUrl,
  secretKey,
  body,
}: {
  apiBaseUrl: string;
  secretKey: string;
  body: unknown;
}): Promise<InitTitleResponse> {
  let res = await fetch(`${apiBaseUrl}/title`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${secretKey}`,
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    throw new Error(`POST /title failed: ${res.status} ${await res.text()}`);
  }

  let json: InitTitleResponse = await res.json();

  while (json.job_status !== "complete" && json.job_status !== "error") {
    await sleep(1500);
    res = await fetch(`${apiBaseUrl}/title/${json.entryId}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${secretKey}`,
      },
    });
    json = await res.json();
  }

  return json;
}

// Example usage:
await postTitleAndWait({
  apiBaseUrl: process.env.API_BASE_URL!,
  secretKey: process.env.CG_SECRET_KEY!,
  body: {
    account_data: {
      vehicle: {
        vin: "19UUA56602A000960",
        title: {
          odometer_reading: "100000",
          issuing_state: "FL",
        },
      },
    },
    sync_nmvtis: true,
    sync_private: true,
  },
});
AccountData parameter vehicle.title.issuing_state tells us where to make a motor vehicle inquiry; if uncertain which state the vehicle is titled in, just set sync_nmvtis: true

Option B: Via /title/{id}/data

For first-time or follow-up data inquiries on existing title entries, the POST /title/{id}/data endpoint is available.
type GetTitleResponse = {
  entryId: string;
  job_status: "enriching" | "complete" | "error";
  nmvtis_last_fetch?: string;
  state_last_fetch?: string;
};

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

export async function postDataRequest({
  apiBaseUrl,
  entryId,
  secretKey,
  sync_nmvtis,
  sync_public,
  sync_private,
  dppa_exemption,
}: {
  apiBaseUrl: string;
  entryId: string;
  secretKey: string;
  sync_nmvtis: boolean;
  sync_public: boolean;
  sync_private: boolean;
  dppa_exemption: string;
}) {
  const res = await fetch(`${apiBaseUrl}/title/${entryId}/data`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${secretKey}`,
    },
    body: JSON.stringify({ sync_nmvtis, sync_public, sync_private, dppa_exemption }),
  });

  if (!res.ok) {
    throw new Error(`POST /title/${entryId}/data failed: ${res.status} ${await res.text()}`);
  }

  return res.json() as Promise<GetTitleResponse>;
}

export async function waitForTitle({
  apiBaseUrl,
  entryId,
  secretKey,
}: {
  apiBaseUrl: string;
  entryId: string;
  secretKey: string;
}) {
  while (true) {
    const res = await fetch(`${apiBaseUrl}/title/${entryId}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${secretKey}`,
      },
    });
    const json = (await res.json()) as GetTitleResponse;
    if (json.job_status === "complete" || json.job_status === "error") return json;
    await sleep(1500);
  }
}

// Example usage:
await postDataRequest({
  apiBaseUrl: process.env.API_BASE_URL!,
  entryId: "YOUR_ENTRY_ID",
  secretKey: process.env.CG_SECRET_KEY!,
  sync_nmvtis: true,
  sync_public: true,
  sync_private: true,
  dppa_exemption: "legitimate_business_need",
});
await waitForTitle({
  apiBaseUrl: process.env.API_BASE_URL!,
  entryId: "YOUR_ENTRY_ID",
  secretKey: process.env.CG_SECRET_KEY!,
});