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:
| Slot | What it identifies | Source signal |
|---|---|---|
dilinkFamily | The 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 |
variantId | The trim badge. Determines which displays exist + which actuators ship. | outswver model code + default_name |
subTrim | The hardware sub-trim. Splits "L5" into Flagship / Navigator / Ultra / Lidar; collapses single-trim variants to base. | outswver model code + dashboard package version |
fingerprint | The 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 enum —
DilinkFamily /
DILINK_FAMILIES.
Sending an unknown value is rejected at the API boundary.
subTrim is closed plus the empty string —
SubTrim /
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
- Import:
import { ProfileKey, ProfileKeySchema } from 'i99dash' - Subpath:
types - File:
src/types/vehicle-capabilities.ts
Related
ProfileKeySchema— the Zod runtime validator.DilinkFamily/DILINK_FAMILIESSubTrim/SUB_TRIMSVehicleCapabilitiesSnapshot— what the backend returns for a key.- Vehicle profile concept — the full model.