i99dash docs
API referencei99dashTypes

ProfileKey

The four-tuple that uniquely identifies a vehicle for capability lookups. Mini-apps read it from `display.list().vehicle`; never construct one by hand.

The identity tuple every CarProfile lookup is keyed on. Hosts derive the active key on boot; mini-apps consume the resolved view via display.list's vehicle block. Backend endpoints accept it as profileKey on read + write paths.

type ProfileKey = {
  dilinkFamily: 'di5.0' | 'di5.1' | 'unknown';
  variantId:    string;   // 'l5' | 'l8' | 'l5l' | 'l7' | 'han_l' | ''
  subTrim:      string;   // 'flagship' | 'navigator' | 'ultra' | 'lidar' | 'base' | ''
  fingerprint:  string;   // ro.build.fingerprint, exact, or ''
};

Why four slots

The tuple is most-coarse → most-precise. Each slot answers a different question:

SlotWhat it identifiesSource signal
dilinkFamilyThe DiLink generation. Shapes which control-plane services exist (com.byd.dishare is Di5.0-only; the cluster ProjectionManager is Di5.1-only).ro.vehicle.type prefix
variantIdThe trim badge. Determines which displays exist + which actuators ship.outswver model code + default_name
subTrimThe hardware sub-trim. Splits "L5" into Flagship / Navigator / Ultra / Lidar; collapses single-trim variants to base.outswver model code + dashboard package version
fingerprintThe exact ROM build. ROM updates can patch behaviour without changing hardware — fingerprint is how the backend keys per-ROM probe results.ro.build.fingerprint

Empty-string slots represent fallback aggregate rows the backend walks server-side; see Vehicle profile concept.

Per-trim examples

What the resolved key looks like on real cars:

// Leopard 8 (single sub-trim, Di5.1)
{
  dilinkFamily: 'di5.1',
  variantId:    'l8',
  subTrim:      'base',
  fingerprint:  'BYD/leopard8/leopard8:13/Q0414/202512071900:user/release-keys',
}

// Leopard 5 Flagship (Di5.0)
{
  dilinkFamily: 'di5.0',
  variantId:    'l5',
  subTrim:      'flagship',
  fingerprint:  'BYD/leopard5/leopard5:12/Q0311/202501132140:user/release-keys',
}

// Leopard 5 Lidar (the lidar variant gets its own trim id)
{
  dilinkFamily: 'di5.0',
  variantId:    'l5l',
  subTrim:      'lidar',
  fingerprint:  '...',
}

// Leopard 7 base
{
  dilinkFamily: 'di5.1',
  variantId:    'l7',
  subTrim:      'base',
  fingerprint:  '...',
}

And what fallback aggregates look like (returned by the backend when the precise tuple hasn't been probed):

// Tier 2 — sub-trim aggregate (UNION across every fingerprint of L5 Flagship)
{ dilinkFamily: 'di5.0', variantId: 'l5', subTrim: 'flagship', fingerprint: '' }

// Tier 3 — trim aggregate (UNION across every L5 sub-trim)
{ dilinkFamily: 'di5.0', variantId: 'l5', subTrim: '', fingerprint: '' }

// Tier 4 — DiLink-default (UNION across every Di5.0 trim)
{ dilinkFamily: 'di5.0', variantId: '', subTrim: '', fingerprint: '' }

Reading the key from a mini-app

Always go through display.list — never call getprop over a shell bridge or try to derive the key yourself:

import { MiniAppClient } from 'i99dash';

const client = MiniAppClient.fromWindow();
const r = await client.display.list();
const key = {
  dilinkFamily: r.vehicle.dilinkFamily,
  variantId:    r.vehicle.variantId,
  subTrim:      r.vehicle.subTrim,
  fingerprint:  r.vehicle.fingerprint,
} satisfies ProfileKey;

The key is process-stable — cache it for the session. The host re-reads only after a probe-driven overlay refresh, which mini-apps don't observe directly.

Sending the key to the backend

When your mini-app POSTs to your backend's probe-ingest or Quick-Report endpoint, nest the key as profileKey. Reach the backend with plain fetch() — declare its origin in manifest.network:

await fetch('https://api.your-service.example.com/v1/car-feedback/correction', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    profileKey: {
      dilinkFamily: r.vehicle.dilinkFamily,
      variantId:    r.vehicle.variantId,
      subTrim:      r.vehicle.subTrim,
      fingerprint:  r.vehicle.fingerprint,
    },
    surface: 'car_action',
    targetId: 'set_seat_massage',
    verdict: 'thumbs_down',
    hostVerdict: 'UNSUPPORTED',
  }),
});

When you're reading (GET), the same fields go on the query string, flat-mapped: ?dilinkFamily=di5.0&variantId=l5&subTrim=flagship&fingerprint=....

What can go in each slot

dilinkFamily is a closed enumDilinkFamily / DILINK_FAMILIES. Sending an unknown value is rejected at the API boundary.

subTrim is closed plus the empty stringSubTrim / SUB_TRIMS. Empty means "trim-level aggregate slot" (a valid wire value, but only used by the backend when it serves a Tier-3 fallback row — clients don't typically send empty here).

variantId and fingerprint are opaque strings — the SDK schema only checks length bounds. The host's CarIdentity chooses the values; mini-apps treat them as identifiers.

Type signature

Prop

Type

Source

On this page