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 (bridge)    │
│ i99dash             │       │  + CSP from manifest.network
└─────────────────────┘       └───────────────┬───────────┘
       ▲                                      │ plain fetch() to
       │ HTML served by                       │ declared origins
       │                                      ▼
┌──────┴───────────┐                   ┌──────────────────────┐
│ i99dash          │                   │  external HTTPS APIs │
│ /dev-server      │                   │  (you or 3rd-party)  │
└──────────────────┘                   └──────────────────────┘

Locally, the 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 for host data

Anything you read from the host — context, car signals — goes through MiniAppClient:

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

const ctx = await client.getContext();           // who, where, how
const climate = await client.car.list({ category: 'climate' });

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.

External APIs are different. To reach an external HTTPS service you use plain fetch() — the SDK isn't involved — and you declare each origin in manifest.network so the host's Content-Security-Policy permits it:

// manifest.json → "network": ["https://api.fuel.example.com"]
const res = await fetch('https://api.fuel.example.com/v1/stations');
const { stations } = await res.json();

See Calling an external API.

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 must be on the host allow-list. Coordinate with ops if you need a custom origin — miniapps.i99dash.app is the v1 default.
  • network declares external egress. Any third-party HTTPS origin your app fetch()es must be listed here, or the host's CSP blocks it. Omit for an app that reaches no external network.
  • icon is bundle-relative, not a URL. Ship the file in your tarball; the publish flow rewrites it to a versioned CDN URL automatically. See App icons recipe.
  • category is a closed enum — see Categories + tags for the 10 canonical slugs.

Full reference: MiniAppManifest.

4. There are two clients

ClientWhen to reach for it
MiniAppClient (from i99dash)Read context, subscribe to car status. Default. (External APIs use fetch(), not the client.)
AdminClient (from i99dash)Run device-side ops (pm.*, sys.*, diag.*, fs.*). Restricted distribution.

If your app only reads context and fetch()es a declared API, MiniAppClient is the whole story. Most apps stop there. Both clients ship in the same i99dash package — mini-apps run with full host capability, no manifest-side gating, and the host arbitrates the actual call at the device.

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

The dev-server (run via pnpm dev) boots a tiny web server that serves your app and attaches the host bridge shim, so getContext() and the car-status simulator work just like on a real car. External fetch() calls hit the real origin (dev doesn't enforce the on-car CSP). Same code path as production: context comes from the bridge, external data comes from fetch(). 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 device ID, 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