i99dash docs
Getting started

What is a mini-app?

The mental model — host, bridge, manifest, bundle. Read once before you start; everything else clicks faster.

In one paragraph

A mini-app is a static web bundle (HTML/JS/CSS) that the i99dash car host loads inside a sandboxed web view. It runs on a head-unit screen mounted in a car. It can read context (which user, which car, which locale, dark mode on/off), it can call backend APIs through a typed proxy, and — if it's a privileged app — it can invoke device-side operations through a separate admin bridge. It can't drive the car or break out of the sandbox.

Architecture

┌─────────────────────┐       ┌───────────────────────────┐
│  your mini-app      │  JS   │  i99dash host             │
│  (HTML / JS / CSS)  │──────▶│  sandboxed web view       │
│                     │       │  + getContext + callApi   │
│ @i99dash/sdk        │       └───────────────┬───────────┘
└─────────────────────┘                       │
       ▲                                      │
       │ fetches / HTML served by             │
       │                                      ▼
┌──────┴───────────┐                   ┌──────────────┐
│ @i99dash/        │   mocks/          │  backend     │
│ sdk-dev-server   │   (./mocks/*.json)│  (production)│
└──────────────────┘                   └──────────────┘

Locally, sdk-dev-server attaches the host bridge to your running page so the same code runs in dev and inside the real host. No if (dev) { ... } branches in your app.

The five things you need to know

1. Your app is just static files

Anything that compiles to static files works: plain HTML, Next.js with output: 'export', Vite, Vue, Svelte, anything else. The CLI tarballs your build directory and uploads it to a CDN. There is no Node runtime in the head-unit; there is no SSR.

2. The bridge is the contract

Every interaction with the outside world goes through MiniAppClient:

import { MiniAppClient } from '@i99dash/sdk';
const client = MiniAppClient.fromWindow();

const ctx = await client.getContext();           // who, where, how
const r = await client.callApi({                 // backend proxy
  path: '/api/v1/fuel-stations',
  method: 'GET',
});

You never call fetch() for backend data — callApi() runs through the host's allow-list. You never read window.location for context — the host injects getContext(). The bridge is the single seam that's testable, mockable, and stable across host versions.

3. The manifest is your catalog row

manifest.json lives at the project root. Three rules the backend won't budge on:

  • id is forever. Pinned home-screen shortcuts on every user's device hold this string. Rotating it orphans every shortcut.
  • version must increment on every publish. Same (id, version) is rejected.
  • url and iconUrl must be on the host allow-list. Coordinate with ops before publishing — they map your origin into the host's allow-list config.

Full reference: MiniAppManifest.

4. There are two SDKs

PackageWhen to reach for it
@i99dash/sdkRead context, call backend APIs, subscribe to car status. Default.
@i99dash/admin-sdkRun device-side ops (pm.*, sys.*, diag.*, fs.*). Restricted distribution.

If your app only reads context and proxies API calls, @i99dash/sdk is the whole story. Most apps stop there. Privileged apps need an admin grant on the developer account before they can even install @i99dash/admin-sdk — it's gated, not just scoped. See Privileged mini-apps.

5. The dev-server makes it the same as production

sdk-dev-server (run via pnpm dev) boots a tiny web server that serves your app, attaches the host bridge shim, and routes callApi() to local fixture files in mocks/*.json. Same code path as production: context comes from the bridge, API calls go through the bridge. You're testing the actual production code, not a mocked subset.

The dev-server's control panel (/_sdk/ui) lets you toggle driving state, change the VIN, switch locale, flip dark mode — every input the real host might inject. Full reference: Local development.

What a mini-app cannot do

  • Actuate the car. No lockDoors(), no setAcOn(true). The SDK is read-only by construction.
  • Persist state on the device. Use localStorage for ephemeral UI state if you must, but treat the bundle as if every launch is the first one.
  • Read other apps' data. Each mini-app is a separate sandboxed web view; there is no DOM access between apps and no shared localStorage.
  • Bypass the manifest. safeWhileDriving, permissions, minHostVersion — the host enforces every one of them. There is no override.

Where to go next

On this page