# Glossary (/docs/glossary) One canonical definition for every i99dash term that appears across the docs. When in doubt, this is the source of truth for terminology. Alphabetical. ### admin-sdk [#admin-sdk] The privileged sibling of `@i99dash/sdk`. Lets a mini-app run device-side commands (`pm.*`, `sys.*`, `diag.*`, `fs.*`). Restricted distribution; gated by a `cmdExec.*` permission grant on the dev account. See [Privileged mini-apps](/docs/develop/privileged-apps). ### allow-list [#allow-list] The host-side configuration of which URL paths a mini-app's `callApi()` can reach. Paths not on the list return `{ success: false, error: { code: 'disallowed_path' } }`. Configured at the host/ops level, not by the mini-app developer. ### app id [#app-id] The first field in `manifest.json`. URL-safe, globally unique, **baked into pinned home-screen shortcuts on every user's device**. Rotating it orphans every shortcut. Pick something you won't regret. ### beta track [#beta-track] A separate catalog pointer that ships a build to a limited cohort of testers (≤ 25 per app) before promoting to production. See [Beta testing](/docs/develop/beta-testing). ### bridge [#bridge] The JavaScript object the host injects on `window.__i99dashHost` (and friends) when it loads a mini-app. The "bridge" is the only seam between your code and the outside world. Your code never touches it directly — `MiniAppClient` wraps it. See [The bridge](/docs/concepts/the-bridge). ### bundle [#bundle] The tarball your CLI uploads to the CDN. Contains your built static files plus `manifest.json`. Identified by a SHA-256; immutable once uploaded. ### callApi [#callapi] The bridge method (and corresponding `client.callApi()` wrapper) for proxying HTTP requests through the host's allow-list. Read-only (GET) in v1. See [Calling your backend](/docs/concepts/calling-your-backend). ### cap (capability token) [#cap-capability-token] In `@i99dash/admin-sdk`: a 30-day token the host stores after the user consents at install. The cap lives in the host's encrypted SQLite, never in the mini-app's bundle. Each privileged op the host attaches the right cap before forwarding to the device-side executor. ### catalog [#catalog] The backend table of published mini-apps. `publish` writes a new row keyed by `(id, version)`; the host's store reads from it. ### context [#context] The snapshot the host returns from `getContext()`: `userId`, `activeCarId` (VIN), `locale`, `isDark`, `appVersion`, `appId`. Stable for the life of one app launch — read once, cache, don't poll. ### device cert [#device-cert] A per-installation certificate the backend mints when a user installs your privileged mini-app. The host attaches it to admin operations; the executor checks it for revocation. Affects which apps a user sees in the catalog. ### dev-server [#dev-server] `@i99dash/sdk-dev-server`, run via `sdk-i99dash dev`. Boots a local HTTP server on port 5173, injects a bridge shim that routes `callApi` to fixture files in `mocks/`, exposes a control panel at `/_sdk/ui` for live driving / VIN / locale / theme toggles. Same code paths as production. ### fixture [#fixture] A JSON file in `mocks/` that the dev-server uses to fake a `callApi` response. Filename ends `.GET.json`; first match alphabetically wins. ### flight test [#flight-test] The pre-flight checklist run during a beta cycle, before promoting to production. Verifies safety gate, context handling, network paths, subscription cleanup, and crash/recovery. See [Flight test](/docs/develop/flight-test). ### gitlink [#gitlink] A git tree entry of mode `160000` that records a submodule's pinned commit SHA. The doc site reads `vendor/sdk-i99dash`'s gitlink at build time to pin the SDK version it generates API pages from. ### head-unit [#head-unit] The car's in-dash display where i99dash runs. Roughly an entry-level phone from 2018. Cold start matters — keep your bundle small. ### host [#host] Singular: the i99dash Flutter app running on the head-unit. It owns the WebView, the bridge, the catalog UI, and the safety gate. Sometimes also used to mean "the host process" or "the host repo" — context disambiguates. ### llms.txt [#llmstxt] The well-known path (`/llms.txt`) where AI tooling looks for a human- and machine-readable index of a doc site. We expose it + `/llms-full.txt` (every page concatenated) + `/llms.mdx/` (per-page markdown). Standard convention for coding agents. ### manifest [#manifest] `manifest.json` at the project root. The catalog row for your app: id, name (per locale), iconUrl, url, version, category, permissions, minHostVersion, safeWhileDriving. The CLI's `validate` command zod-checks it. ### MCP [#mcp] Model Context Protocol. The wire spec coding assistants speak to fetch external context. We expose a Streamable-HTTP MCP server at `/api/mcp` with five tools (`list_sections`, `list_packages`, `list_symbols`, `search_docs`, `get_doc`). See [MCP server](/docs/develop/mcp-server). ### minHostVersion [#minhostversion] A field in `manifest.json` that gates which host versions can install your mini-app. Bump it the moment you start using a new host feature; the catalog hides your app from users on older hosts instead of installing-then-crashing. ### mini-app [#mini-app] A static HTML/JS/CSS bundle the i99dash host loads inside a sandboxed WebView. The thing this whole platform exists to ship. See [What is a mini-app?](/docs/getting-started/what-is-a-mini-app). ### mock / mocks [#mock--mocks] Synonym for fixture in dev-server context. The directory `mocks/`; the files `mocks/*.json`. ### permissions [#permissions] `manifest.permissions[]`. Strings the host enforces. `car.status.read` is implicit for v1 mini-apps; `cmdExec.read` and `cmdExec.control` are the privileged grants for `@i99dash/admin-sdk` apps. See [Privileged mini-apps](/docs/develop/privileged-apps#manifest-declaration). ### production track [#production-track] The default catalog pointer. Every user sees the bundle on next launch. Counterpart to the beta track. ### safeWhileDriving [#safewhiledriving] A manifest boolean. When `true`, the host shows your app while the car is moving (≥ \~5 km/h). When `false` or unset, the host shows "not available while driving" instead. Choose carefully; affects the safety review. ### sdk-cli [#sdk-cli] `@i99dash/sdk-cli`. The CLI invoked as `sdk-i99dash ` (or via `pnpm dlx @i99dash/sdk-cli`). Provides `init`, `dev`, `validate`, `build`, `publish`, `login`, `logout`, `whoami`, and the `beta *` subtree. ### staleness [#staleness] `status.staleness` in a `CarStatus` payload. One of `'fresh'` (\< 15 s), `'stale'` (15–60 s), `'very_stale'` (> 60 s). Independent of the connection-state signal — render both. ### step-up [#step-up] The fresh user prompt the host shows for tier-2 admin operations (`sys.reboot`, `pm.install`) at action time, even when install consent was previously granted. Privacy + safety belt-and-braces. ### submodule [#submodule] `vendor/sdk-i99dash` in the doc repo. A git submodule pinned to a specific SDK commit. The doc site builds API reference from this exact pinned source — bumps happen via PR (see `bump-sdk-submodule.yml`), keeping deploys reproducible. ### tester [#tester] A user explicitly added to a mini-app's beta cohort via `beta invite`. Identified by Telegram username on the dev side, by internal user-id on the host side. ### tier (admin-sdk) [#tier-admin-sdk] A property of every `CommandTemplate`. Tier 1 = read-only ops (`diag.tail_logs`, `pm.list_*`); Tier 2 = writes/state changes (`pm.disable_user`, `sys.reboot`). Higher tiers gate more permissions. ### track [#track] The catalog has two tracks per app: production and beta. Each is a single-bundle pointer. `publish --track beta` lands on beta; `beta promote-production` copies beta → production. ### VIN [#vin] Vehicle Identification Number. The `activeCarId` field in the context. PII in some jurisdictions — never render in plain text; mask all but the last 4 digits. --- # i99dash for developers (/docs) Build mini-apps that run on i99dash car head-units. From zero to published in five minutes. i99dash hosts third-party **mini-apps** on a car head-unit. A mini-app is a plain HTML / JS / CSS bundle the host loads inside a sandboxed web view. It reads context (active user, current car, locale, theme) and proxies API calls through a typed JS bridge — `getContext()` and `callApi()`. ## Should I build a mini-app? [#should-i-build-a-mini-app] Yes if you want to: * Show information to drivers (fuel prices, weather, EV charging stations, parking lots) — these are **read-only** mini-apps, public catalog. * Build a privileged tool (diagnostics, log tailing, package management) for fleet operators or your own team — these are `@i99dash/admin-sdk` mini-apps, restricted distribution. No if you want to: * Control the car (lock doors, set AC, drive). The SDK is read-only by construction. There is no `client.car.lockDoors()`. * Build a native head-unit app. Mini-apps are HTML/JS — for native, talk to the host team directly. ## Start here [#start-here] ## Reference, when you need it [#reference-when-you-need-it] ## What's new [#whats-new] * **Beta testing track** — publish to a limited cohort first (TestFlight-style). [Read the guide](/docs/develop/beta-testing). ## Working with AI agents [#working-with-ai-agents] The site exposes [`/llms-full.txt`](/llms-full.txt) (every page as plain markdown) and an MCP server at [`/api/mcp`](https://docs.i99dash.app/api/mcp) with tools `search_docs`, `get_doc`, `list_packages`, `list_symbols`. Add it to Claude Code: ```bash claude mcp add --transport http i99dash-docs https://docs.i99dash.app/api/mcp ``` --- # API reference (/docs/api) Auto-generated TypeScript reference for every public symbol across the i99dash SDK monorepo. {/* @manual */} These pages are generated directly from TypeScript source in `sdk-i99dash/packages/*/src/**`. JSDoc on the source is the single source of truth — if a description here is wrong, fix the source comment. ## Why pages can look sparse [#why-pages-can-look-sparse] Each API page is intentionally one line of prose plus a `` that renders the type from source. Long-form explanation lives in [guides](/docs/guides), which link back to the relevant API page. ## How to update [#how-to-update] 1. Edit the JSDoc on the symbol in `sdk-i99dash/packages/*/src/**`. 2. Rebuild: `pnpm dev` regenerates the table on the next request; `pnpm build` regenerates statically. --- # Calling your backend (/docs/concepts/calling-your-backend) The `callApi()` mental model. Why it isn't `fetch()`. How the host's allow-list works. The two failure shapes. `client.callApi()` is the proxy your mini-app uses to reach an HTTP backend. It looks like `fetch()` but isn't — there are two important differences worth internalising before you write your first call. ## The two big differences from `fetch()` [#the-two-big-differences-from-fetch] ### 1. The host has an allow-list [#1-the-host-has-an-allow-list] Your mini-app **cannot** make arbitrary HTTP requests. Every `callApi()` request is dispatched through the host, which checks the target path against an **allow-list** before forwarding it. Paths not on the list return `{ success: false, error: { code: 'disallowed_path' } }`. Why: a public mini-app bundle is downloaded by every user. If it could reach `evil.example.com/exfil`, every user's car becomes a data-exfiltration vector. The allow-list closes that. #### `path` is not a URL — it's a route key [#path-is-not-a-url--its-a-route-key] ```ts client.callApi({ path: '/api/v1/fuel-stations', method: 'GET' }); // ^─ NOT a URL. // The host resolves this at call time: // // Local dev: a JSON fixture in mocks/ // Production: your HTTPS backend, mapped by // i99dash ops to the path prefix ``` The mapping table lives on the host side. Your mini-app's code does not change between dev and prod — same `path`, the host swaps what it forwards to. #### You bring your own backend [#you-bring-your-own-backend] The i99dash platform's own backend API is **internal infrastructure** for the host itself — not a developer surface. There is no shared "platform endpoint" you can call for free. Every mini-app's `callApi` paths resolve to either a fixture (in dev) or a service the developer brought (in prod). The flow: 1. Stand up an HTTPS service that returns `{ success, data | error }` envelopes. 2. Declare the path prefix in `manifest.permissions[]`, e.g. `"callApi:/api/v1/my-feature"`. 3. Coordinate with i99dash ops: send them the path prefix + your service URL. Ops adds the mapping to the host's allow-list. Your app only declares **what paths it needs** — the mapping to **where they live** is host config, controlled centrally. You never put a service URL in the mini-app's code or in `manifest.json`. Full walkthrough with a runnable Hono example: [Recipe — Going to production with your own backend](/docs/recipes/fuel-prices#going-to-production-with-your-own-backend). ### 2. Failures are **data**, not exceptions [#2-failures-are-data-not-exceptions] ```ts // Wrong — assumes success or throws on failure try { const r = await client.callApi({ path: '/api/v1/x', method: 'GET' }); return r.data; // ← what if success was false? } catch { return null; } // Right — branch on the envelope const r = await client.callApi({ path: '/api/v1/x', method: 'GET' }); if (!r.success) { // r.error.code is one of: disallowed_path, http_4xx, http_5xx, // network_error, timeout, ... return handleFailure(r.error); } return r.data; ``` `callApi()` only **throws** for genuine transport failures (bridge crashed, timeout exceeded, malformed envelope from the host). Every documented failure mode — disallowed path, backend 4xx, backend 5xx, network down, timeout — comes back as `{ success: false, error: ... }`. This is intentional. `try/catch` for branching makes happy-path unreadable; branching on `success` is symmetric and forces you to think about the failure case. ## Anatomy of a request [#anatomy-of-a-request] ```ts import { type CallApiRequest } from '@i99dash/sdk'; const req: CallApiRequest = { path: '/api/v1/fuel-stations', // path the host's allow-list checks method: 'GET', // 'GET' is the only verb in v1 query: { lat: 24.7, lng: 46.6 }, // optional; serialised as URL query headers: { 'X-Trace': 'abc' }, // optional; forwarded to backend }; ``` | Field | Required? | Notes | | --------- | --------- | ----------------------------------------------------------------------------------------------------- | | `path` | yes | Must match a pattern on the host's allow-list. Origin is implied — you don't send a full URL. | | `method` | yes | `GET` only in v1. Read-only design — see [Why no POST/PUT/DELETE](#why-no-postputdelete). | | `query` | no | Plain object. Numbers are stringified; arrays serialise as `key=v1&key=v2`. | | `headers` | no | Forwarded to the backend. Don't put secrets here — see [The bundle is public](#the-bundle-is-public). | The host adds the rest: * The user's session token (you don't manage auth tokens client-side). * A correlation ID for tracing. * Origin headers the backend can use to enforce its own rules. ## Anatomy of a response [#anatomy-of-a-response] ```ts import { type CallApiResponse } from '@i99dash/sdk'; type CallApiResponse = | { success: true; data: T } | { success: false; error: { code: string; message: string } }; ``` The discriminant is `success`. TypeScript narrows correctly: ```ts const r = await client.callApi<{ stations: Station[] }>({ path: '/api/v1/fuel-stations', method: 'GET', }); if (r.success) { // TypeScript knows r.data is { stations: Station[] } renderStations(r.data.stations); } else { // TypeScript knows r.error.code is string if (r.error.code === 'disallowed_path') reportConfigBug(); else renderRetry(); } ``` ## Common error codes [#common-error-codes] | `r.error.code` | What it means | What to render | | ----------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------- | | `disallowed_path` | The host rejected your path. The allow-list doesn't include it. | Log + report — this is a config bug, not a user bug. | | `http_4xx` | Backend returned a 4xx (typically auth or input). Body is in `r.error.message`. | Surface the backend's message to the user. | | `http_5xx` | Backend returned a 5xx. | Generic "service unavailable, try again". | | `network_error` | The host couldn't reach the backend at all. | "No connection" UI. | | `timeout` | Request exceeded the host's timeout (separate from the SDK's timeout). | Same as `network_error` for users; log separately for ops. | Backend-specific codes (e.g. `fuel.station_not_found`) are also possible when your backend responds with a 4xx and a structured body. Forward those to your error-rendering code as-is. ## Patterns [#patterns] ### Polling [#polling] ```ts async function poll() { while (!cancelled) { const r = await client.callApi({ path: '/api/v1/x', method: 'GET' }); if (r.success) update(r.data); await sleep(5_000); } } ``` `callApi`'s default 10-second timeout means a hung request only blocks the loop for that long. No backpressure / dedup logic needed for poll-rate ≥ 10s. ### Cancellation on unmount [#cancellation-on-unmount] ```ts useEffect(() => { const controller = new AbortController(); client.callApi({ path: '/api/v1/x', method: 'GET' }, { signal: controller.signal }) .then(r => { if (r.success) setData(r.data); }); return () => controller.abort(); // ← cancels in-flight on unmount }, []); ``` The signal is forwarded to the bridge. Aborting before the host responds rejects with the signal's reason (DOM-standard `AbortError`). ### Custom timeout [#custom-timeout] ```ts const r = await client.callApi( { path: '/api/v1/big-report', method: 'GET' }, { timeoutMs: 60_000 }, // 60s for a slow report endpoint ); ``` ### Defensive read on the response shape [#defensive-read-on-the-response-shape] Backends evolve. A field you depend on might be optional in the schema: ```ts const r = await client.callApi<{ stations?: Station[] }>(req); if (r.success) { for (const s of r.data.stations ?? []) { // ... } } ``` `callApi` validates the envelope (the `success`/`data` shape). It does **not** validate `data` against your `T` — that's your contract with your backend. Use zod on the client if you want runtime validation. ## Why no POST/PUT/DELETE [#why-no-postputdelete] The SDK is **read-only by construction**, same reason there's no `client.car.lockDoors()`. Mutations require a different review and permission model than reads. If a future SDK version supports a write surface, it will be a separate explicit method, not a flag on `callApi`. ## The bundle is public [#the-bundle-is-public] Anything you embed in your bundle — API keys, header secrets, logic — is visible to every user who installs your mini-app. `unzip + strings` extracts it. This means: * Don't put auth tokens in `headers`. The host injects the session token; that's the only auth you need. * Don't put database credentials, Stripe keys, or service-role tokens anywhere in your code. Fetch them server-side after the user authenticates. * Treat your `callApi` paths as a public surface. If a path leaks more than the user should see, it's a backend bug, not a client one. ## Related [#related] * [`callApi` reference](/docs/api/sdk/call-options) — type details. * [`CallApiRequest`](/docs/api/types/call-api-request) — wire schema. * [`CallApiResponse`](/docs/api/types/call-api-response) — envelope. * [Best practices](/docs/guides/best-practices#errors) — production error-handling patterns. * [Error model](/docs/concepts/error-model) — when callApi throws vs returns a failure envelope. --- # Error model (/docs/concepts/error-model) Two failure shapes, seven error classes, one decision flow. Understand the SDK's error contract once and stop second-guessing. The SDK has two kinds of failure: **thrown errors** and **error envelopes**. They surface different things and want different handling. Once you know which is which, every error in the SDK follows the same pattern. ## The two shapes [#the-two-shapes] ``` ┌──────────────────────────────┐ │ bridge call (e.g. callApi) │ └──────────────┬───────────────┘ │ ┌───────────────┴───────────────┐ │ │ ┌─────────▼──────────┐ ┌──────────▼─────────┐ │ THROWN │ │ RETURNED IN ENVELOPE │ • bridge crashed │ │ • backend said no │ │ • timeout │ │ • path not allowed │ │ • malformed reply │ │ • 4xx / 5xx │ │ • not in host │ │ • offline / timeout │ │ │ │ (host's, not SDK's) │ try / catch this │ │ if (!r.success) ... │ └────────────────────┘ └─────────────────────┘ ``` ### Thrown — programmer or environment issues [#thrown--programmer-or-environment-issues] The SDK throws when *something is wrong with the runtime itself*: * The bridge isn't there (`NotInsideHostError`). * The bridge call timed out (`BridgeTimeoutError`). * The host returned a malformed payload (`InvalidResponseError`). * The bridge transport itself crashed (`BridgeTransportError`). * The car-status surface isn't supported on this host (`CarStatusUnavailableError`, `CarStatusQuotaExceededError`). * Privileged template not in your catalog (`UnknownTemplateError`). These are **exceptional**: you typically don't recover, you log and render a fallback. ### Returned — operational failures [#returned--operational-failures] `callApi()` and admin `invoke()` return `{ success: false, error: ... }` when the *operation* failed but the bridge worked. This is **data**: ```ts const r = await client.callApi({ path: '/api/v1/x', method: 'GET' }); if (!r.success) { // r.error.code: 'disallowed_path' | 'http_4xx' | 'http_5xx' | ... } ``` Don't try/catch around `if (!r.success)` — that conflates two different failure modes and loses information. ## The seven SDK error classes [#the-seven-sdk-error-classes] | Class | When | What to do | | ---------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `NotInsideHostError` | No bridge on `window`. SSR, Storybook, Node. | Catch and render a fallback UI. Most common in test setups. | | `BridgeTimeoutError` | Host bridge didn't respond in time. Default 10 s. | Retry once with backoff, or surface "device unresponsive". `error.operation` says which call timed out. | | `BridgeTransportError` | Bridge layer crashed mid-call. Host bug. | Log, report. Usually transient — retry with backoff. Original cause is in `error.cause`. | | `InvalidResponseError` | Host returned a payload that doesn't match the SDK's schema. | Log + report. Means host/SDK version drift. Don't retry — same payload will fail again. | | `CarStatusUnavailableError` | Bridge isn't a `CarStatusBridge` (older host, test stub). | Catch on `client.car.getStatus()` etc.; render a fallback. | | `CarStatusQuotaExceededError` | More than 10 concurrent `onStatusChange` subs from this mini-app. | Find the leak. Almost always a missing `useEffect` cleanup. | | `UnknownTemplateError` (admin-sdk) | `templateId` not in your catalog snapshot. | Bug in your code or a stale catalog. Refresh the catalog or check the template name. | All seven extend `SDKError` and carry a stable `code` field: | Class | `code` | | ---------------------------------- | ----------------------------- | | `NotInsideHostError` | `'NOT_INSIDE_HOST'` | | `BridgeTimeoutError` | `'BRIDGE_TIMEOUT'` | | `BridgeTransportError` | `'BRIDGE_TRANSPORT'` | | `InvalidResponseError` | `'INVALID_RESPONSE'` | | `CarStatusUnavailableError` | `'CAR_STATUS_UNAVAILABLE'` | | `CarStatusQuotaExceededError` | `'CAR_STATUS_QUOTA_EXCEEDED'` | | `UnknownTemplateError` (admin-sdk) | `'UNKNOWN_TEMPLATE'` | ## The decision flow [#the-decision-flow] ``` ┌──────────────────┐ │ caught an error? │ └────────┬─────────┘ │ ┌──────────────┴───────────────┐ │ │ ┌───▼────┐ ┌──────▼──────┐ │ thrown │ │ envelope │ │ (try/ │ │ (r.success │ │ catch) │ │ === false) │ └───┬────┘ └──────┬──────┘ │ │ switch (e.code) { switch (r.error.code) { NOT_INSIDE_HOST: disallowed_path: ▶ render fallback ▶ log; config bug BRIDGE_TIMEOUT: http_4xx: ▶ retry / banner ▶ surface message CAR_STATUS_UNAVAILABLE: http_5xx: ▶ hide widget ▶ retry / banner CAR_STATUS_QUOTA_EXCEEDED: network_error: ▶ check for leak ▶ "no signal" INVALID_RESPONSE: timeout: ▶ log + report ▶ same as network BRIDGE_TRANSPORT: : ▶ log + retry ▶ render per-code } } ``` ## Switch on `code`, not `instanceof` [#switch-on-code-not-instanceof] ```ts // Wrong — coupling to class names if (e instanceof BridgeTimeoutError) { ... } else if (e instanceof BridgeTransportError) { ... } // Right — switch on the stable code import { SDKError } from '@i99dash/sdk'; if (e instanceof SDKError) { switch (e.code) { case 'BRIDGE_TIMEOUT': return retryWithBackoff(); case 'BRIDGE_TRANSPORT': return reportTransient(e.cause); case 'NOT_INSIDE_HOST': return renderFallback(); // ... } } throw e; ``` `code` is part of the public API. New codes only get added in minor releases; existing codes never change meaning. Class names work too, but they're harder to switch on cleanly when you have many cases. ## `cause` carries the underlying error [#cause-carries-the-underlying-error] Every wrapped error sets `cause` per the ES2022 `Error` spec: ```ts catch (e) { if (e instanceof BridgeTransportError) { Sentry.captureException(e, { extra: { cause: e.cause } }); } } ``` Sentry, modern browser DevTools, and most logging libraries walk the `.cause` chain automatically. Don't redact it — the original cause is the actionable part. ## When to retry [#when-to-retry] | Class / code | Retry? | How | | --------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | | `BRIDGE_TIMEOUT` | Maybe — once with backoff. Persistent timeout = host hung. | Single 500-1000 ms delay then retry. Two failures → surface. | | `BRIDGE_TRANSPORT` | Yes — usually transient. | Exponential backoff (200 → 400 → 800 ms). | | `INVALID_RESPONSE` | **No.** | The host will return the same broken payload. Log and stop. | | `NOT_INSIDE_HOST` | **No.** | Permanent on this runtime. | | `CAR_STATUS_UNAVAILABLE` | **No.** | Older host or test stub. Render fallback. | | `CAR_STATUS_QUOTA_EXCEEDED` | **No.** | Find the leak. | | envelope `http_5xx` | Yes. | Up to backend's idempotency contract. | | envelope `network_error` | Yes — typically with longer backoff. | Show progress. | | envelope `disallowed_path` | **No.** | Config bug; retry won't fix it. | ## Related [#related] * [Best practices — Errors](/docs/guides/best-practices#errors) — code-style examples for each pattern. * [Errors reference](/docs/api/sdk/errors) — auto-generated from the `SDKErrorCode` union. * [Calling your backend](/docs/concepts/calling-your-backend) — the envelope contract for `callApi`. * [Subscriptions](/docs/guides/subscriptions#errors) — error model for `client.car.*` specifically. --- # Concepts (/docs/concepts) The mental models you need to read SDK code without surprises. One page per non-obvious thing. If the [Quickstart](/docs/getting-started) gets you running, this section gets you *fluent*. Each page is short and answers one question: These pages don't replace the [reference](/docs/api) — they're the prose your IDE's hover tooltips can't show. --- # The bridge (/docs/concepts/the-bridge) What `MiniAppClient` actually wraps, why your code never touches it directly, and how to fake it in tests. When the i99dash host loads your bundle, it injects a JavaScript object on `window` called the **bridge**. Every conversation between your code and the outside world crosses this one object — context reads, backend calls, car-status subscriptions. You almost never touch the bridge directly. `MiniAppClient` wraps it with types, timeouts, schema validation, and error wrapping. ## The mental model [#the-mental-model] ``` ┌────────────────┐ ┌─────────────────────┐ │ your code │ │ host process │ │ │ │ (Flutter app) │ │ MiniAppClient ─┼──── JS bridge ────▶│ getContext() │ │ │ window.__i99dash │ callApi() │ │ │ (a JS object) │ subscribeCarStatus()│ └────────────────┘ └─────────────────────┘ ``` The bridge is **not a network call**. It's an in-process JavaScript object the host populated before your bundle ran. Calls cross the WebView ↔ native boundary inside the host (Flutter platform channels), but from your code's perspective they're plain async function calls. ## Why a wrapper? [#why-a-wrapper] You could in theory call `window.__i99dashHost.getContext()` directly. You shouldn't, because `MiniAppClient` adds: | What the wrapper adds | Why it matters | | ------------------------------------ | ---------------------------------------------------------------------------------------- | | TypeScript types on every call | Auto-complete + compile-time guarantees | | Schema validation (zod) | Host bumps that drop a field → loud `InvalidResponseError` instead of silent `undefined` | | 10-second default timeout per call | Host hangs don't propagate to your UI as forever-spinners | | `AbortSignal` integration | Cancel an in-flight call when a user navigates away | | Wrapped errors with `code` + `cause` | Switch on a stable `code`; preserve the underlying reason for Sentry | | Lazy car-status surface | No bridge subscriptions opened until you actually call `client.car.*` | Reaching past it (e.g., `(window as any).__i99dashHost`) bypasses all of those. Don't. ## Two ways to construct a client [#two-ways-to-construct-a-client] ```ts import { MiniAppClient } from '@i99dash/sdk'; // Production: read the bridge from window. Throws NotInsideHostError // if there's no bridge (SSR, Storybook, jsdom). const client = MiniAppClient.fromWindow(); ``` ```ts // Tests / dev / SSR: pass a fake bridge. Any object that satisfies // the Bridge interface works. const client = MiniAppClient.withBridge({ getContext: async () => ({ userId: 'test-user', activeCarId: 'TEST-VIN', locale: 'en', isDark: false, appVersion: '1.0.0', appId: 'my-app', }), callApi: async () => ({ success: true, data: null }), }); ``` The `Bridge` interface is the contract. As long as your fake matches, the rest of the SDK runs unchanged. See [Testing](/docs/guides/testing) for the full pattern. ## Surfaces the bridge exposes [#surfaces-the-bridge-exposes] | Bridge method | What for | SDK wrapper | | ----------------------------------------------------------------------- | -------------------------------------- | ----------------------------------- | | `getContext()` | Read user / VIN / locale / theme | `client.getContext()` | | `callApi(req)` | Backend HTTP through host's allow-list | `client.callApi(req)` | | `getCarStatus()` | One-shot car snapshot (host ≥ 0.0.2) | `client.car.getStatus()` | | `subscribeCarStatus(cb)` / `unsubscribeCarStatus(id)` | Streaming updates | `client.car.onStatusChange(cb)` | | `subscribeCarConnectionState(cb)` / `unsubscribeCarConnectionState(id)` | Stream connection up/down | `client.car.onConnectionChange(cb)` | If your runtime has a `Bridge` but not a `CarStatusBridge` (older host, test stub), the streaming methods throw `CarStatusUnavailableError` — catch it and render a fallback. ## What the bridge **doesn't** expose [#what-the-bridge-doesnt-expose] * **No actuators.** No `lockDoors()`, no `setAcOn()`, no driving controls. The SDK is read-only by construction. Privileged ops live behind `@i99dash/admin-sdk` and require explicit user consent + server-side capability tokens — see [Privileged mini-apps](/docs/develop/privileged-apps). * **No DOM access between mini-apps.** Each mini-app is a separate sandboxed WebView. There's no shared `localStorage`, no `postMessage` between them. * **No persistent storage.** Treat each launch as the first one. `localStorage` works for ephemeral UI state, but the host can clear it at any time. ## How the dev-server fits in [#how-the-dev-server-fits-in] `sdk-dev-server` (run via `pnpm dev`) injects a **bridge shim** on `window` before serving your page. The shim implements the same contract as the real host bridge but routes: * `getContext()` → values from `sdk.config.json`'s `dev.context` block (overridable live from the control panel at `http://127.0.0.1:5173/_sdk/ui`). * `callApi()` → fixture files in `mocks/*.json`. * Car-status methods → an in-process simulator the control panel drives. Your code is identical in dev and prod — `MiniAppClient.fromWindow()` finds either bridge transparently. There is no `if (dev)` branch. ## Related [#related] * [`MiniAppClient`](/docs/api/sdk/client) — full type signature. * [`Bridge`](/docs/api/sdk/bridge) — the interface, for fake bridges. * [Testing](/docs/guides/testing) — how to write `withBridge` tests. * [Local development](/docs/develop/local-dev) — fixture grammar + control panel. --- # Authentication (/docs/develop/authentication) Device-code OAuth login, where the API key lives, and how to provision CI / headless machines. You need an API key to **publish** a mini-app. You do **not** need one to develop locally — the dev-server has no network dependency on the i99dash backend. ## Interactive (recommended) [#interactive-recommended] ```bash sdk-i99dash login ``` The CLI uses [OAuth 2.0 Device Authorization Grant (RFC 8628)](https://datatracker.ietf.org/doc/html/rfc8628): 1. The CLI asks the backend for a *device code* and a *user code*. 2. The CLI prints a URL and the user code, and opens the URL in your browser. 3. You log in to i99dash, paste the user code, and approve. 4. The CLI polls the backend. On approval it receives a long-lived API key and stores it in your OS keychain. Nothing about your password / session cookie ever touches the CLI. ## Where the key lives [#where-the-key-lives] | Platform | Store | | ------------------------- | --------------------------------------------------------------- | | macOS | Keychain Access, service `i99dash.sdk`, account `default` | | Windows | Credential Manager, generic credential `i99dash.sdk` | | Linux (with libsecret) | libsecret collection `default`, schema `i99dash.sdk` | | Linux (without libsecret) | `~/.config/i99dash/sdk.json`, mode `0600`, with console warning | Check via `sdk-i99dash whoami`. ## Log out [#log-out] ```bash sdk-i99dash logout ``` Purges the keychain entry (or deletes the fallback file). ## CI / headless machines [#ci--headless-machines] Skip `login` entirely. Set: ```bash export I99DASH_API_KEY="" ``` The CLI prefers `I99DASH_API_KEY` over the keychain, so a CI runner with the env var set behaves identically to a logged-in dev. To mint a CI key: run `sdk-i99dash login` on a dev machine, then copy the key out of the keychain and store it as a CI secret. Rotate periodically — see below. ## Rotation [#rotation] 1. `sdk-i99dash logout` (clears the local copy) 2. `sdk-i99dash login` again (mints a new one) 3. The old key is invalidated server-side. For shared service accounts, coordinate rotation across your team — there's no per-seat key in v1. ## What the CLI does with the key [#what-the-cli-does-with-the-key] Exhaustively — network calls with the key attached: 1. `GET /api/v1/dev/me` (identity probe). 2. `POST /api/v1/mini-apps/upload-url` (publish). 3. `PUT ` (publish, no auth header — presigned URL is the credential). 4. `POST /api/v1/mini-apps/submit` (publish). 5. `GET /api/v1/mini-apps/mine` (listing your apps; optional). Nothing else. --- # Beta testing (/docs/develop/beta-testing) Publish a build to a small group of Telegram-verified testers before the public catalog. TestFlight-style workflow for i99dash mini-apps. The CLI ships a beta track for every mini-app. You publish a build to a limited group of testers, iterate based on their feedback, then promote the same bundle to production when you're happy. This is the equivalent of TestFlight for i99dash mini-apps. ## When to use it [#when-to-use-it] * You're shipping a behaviour change that's risky on the head-unit (UI rewrite, new bridge feature, backend dependency that just landed). * You want a sample of real users to validate before everyone gets it. * You need to demo a build to a stakeholder without flipping the production catalog. If your release is documentation-only or copy-tweaks, skip beta and publish straight to production. ## Limits [#limits] | Limit | Why | | ----------------------------- | ---------------------------------------------------------------------------------------------- | | **25 testers per app** | Including yourself. Backend returns `beta.tester_cap_reached` (HTTP 422). | | **90-day beta expiry** | A beta build expires 90 days after promotion. Promote again or move to production before then. | | **One beta bundle at a time** | The beta track is a single pointer per app. Promoting a new build replaces the previous one. | ## Two ways to put a build on the beta track [#two-ways-to-put-a-build-on-the-beta-track] ### Path 1 — publish directly to beta [#path-1--publish-directly-to-beta] ```bash sdk-i99dash publish --track beta --release-notes "Rebased on latest map tiles" ``` Same flow as a regular `publish`, except the bundle lands on the beta track instead of production. Testers see this build automatically the next time they launch the mini-app. `--release-notes` is shown in the consent sheet the host pops up when a tester first opens a new beta build. Keep it short — one sentence is ideal. ### Path 2 — publish to production, promote later [#path-2--publish-to-production-promote-later] ```bash sdk-i99dash publish # lands on production sdk-i99dash beta promote com.example.myapp 2.0.0 --release-notes "Nightly build" ``` Use this when: * You publish on a CI tag and decide the beta cohort by hand later. * You want to roll back the beta to a prior version that's already in the catalog. ## Manage the tester roster [#manage-the-tester-roster] ### Invite a tester [#invite-a-tester] ```bash sdk-i99dash beta invite com.example.myapp @alice sdk-i99dash beta invite com.example.myapp alice # @ prefix is optional ``` The CLI strips a leading `@` so you don't have to think about it. ### Invite multiple testers in one shot [#invite-multiple-testers-in-one-shot] ```bash # Space-separated sdk-i99dash beta invite-batch com.example.myapp alice bob charlie # Comma-separated (also works) sdk-i99dash beta invite-batch com.example.myapp "alice,bob,charlie" ``` Use this for the initial roster on a fresh beta — one round-trip instead of N. ### List the roster [#list-the-roster] ```bash sdk-i99dash beta testers com.example.myapp ``` Output: ``` USER ID TELEGRAM STATUS INVITED AT ACCEPTED AT ------------------------------------------------------------------------------------------------ 01J9VWCB9G… @alice accepted 2026-04-20 14:02 UTC 2026-04-20 14:11 UTC 01J9VWCB9H… @bob invited 2026-04-20 14:02 UTC — ``` `STATUS` values: | Status | Meaning | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `invited` | We've recorded the invite. The user hasn't opened i99dash since the invite, **or** the username never existed (you can't tell which from this row — see Security below). | | `accepted` | The user has consented to receive beta builds. They'll see the beta on next launch. | | `revoked` | You removed them via `beta revoke`. Stays in the table for audit. | ### Remove a tester [#remove-a-tester] ```bash sdk-i99dash beta revoke com.example.myapp ``` `user_id` is the first column of the `beta testers` table — copy it verbatim. The tester loses access on next launch; their pinned shortcut still works but reverts to the production build. ## Promote the beta to production [#promote-the-beta-to-production] Two ways: ```bash # Copy the current beta bundle into the production track sdk-i99dash beta promote-production com.example.myapp ``` This is the **fast path** when the beta passed review. The current beta bundle becomes the new production version with no rebuild — same SHA-256, same CDN URL, same `(id, version)` pair. ```bash # Or pull the beta without promoting (e.g. you found a critical bug) sdk-i99dash beta demote com.example.myapp ``` This clears the beta pointer. Testers fall back to production on next launch. ## End-to-end example [#end-to-end-example] Here's the full lifecycle of a single beta cycle: ```bash # Tag a release in CI; it publishes to production on tag push git tag v2.0.0 && git push --tags # Decide which version to beta — pick a recent one sdk-i99dash beta promote com.example.myapp 2.0.0 \ --release-notes "v2: redesigned home, new fuel widget" # Add your testers sdk-i99dash beta invite-batch com.example.myapp \ alice bob charlie diana eve # Watch acceptance over the next day or two sdk-i99dash beta testers com.example.myapp # After feedback + a fix git tag v2.0.1 && git push --tags sdk-i99dash beta promote com.example.myapp 2.0.1 \ --release-notes "v2.0.1: fixes the home-screen tap target" # Once everyone's happy, ship to production sdk-i99dash beta promote-production com.example.myapp ``` ## Security: account enumeration mitigation [#security-account-enumeration-mitigation] The `beta invite` and `beta invite-batch` endpoints always return **HTTP 200**, regardless of whether the supplied Telegram username exists. The CLI prints `Invite recorded` either way. This is deliberate: a public-facing dev tool that returned different responses for "real account" vs "no such user" would let anyone enumerate i99dash's user base by guessing usernames. Real status only shows in the `beta testers` table, which **requires your developer API key** — that gate is what stops enumeration. If you need to confirm an invite landed: 1. Wait for the user to open i99dash. 2. Run `beta testers ` and look for their row. 3. If their status is still `invited` after 24 h, ask them to check the i99dash app's "Mini-apps" tab — there should be a notification. ## How a tester actually receives the beta [#how-a-tester-actually-receives-the-beta] From the user's side: 1. Opens i99dash on their car or phone. 2. Sees a **Beta available** card on the mini-app the dev invited them to. 3. Taps the card → consent sheet (with your `--release-notes` text). 4. Approves → next launch loads the beta bundle. The tester can flip back to production at any time from the same card. You don't need to do anything for the rollback path — it's part of the host's beta UX. ## Errors [#errors] | Code | When | Fix | | ------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- | | `beta.tester_cap_reached` | You hit 25 testers + 1 dev. | Revoke someone first, or wait until a tester accepts and replaces a stale invite. | | `beta.no_active_bundle` | `beta promote-production` ran but no beta bundle exists. | Promote a version to beta first. | | `beta.expired` | The beta has been live for >90 days. | Promote a fresh version to refresh the timer. | | `beta.unknown_app` | `app_id` doesn't exist or you don't own it. | Check `app_id` matches your `manifest.json`. | | `beta.tester_not_found` | `beta revoke` ran with a user\_id that isn't on the roster. | Run `beta testers` and copy the user\_id verbatim. | ## Related [#related] * [Publishing](/docs/getting-started/publishing) — full publish flow, including how `--track` interacts with the existing pipeline. * [Authentication](/docs/develop/authentication) — beta commands need the same API key. * [`runPublish` reference](/docs/api/sdk-cli/run-publish) — programmatic entry point for CI. --- # Flight test (/docs/develop/flight-test) The pre-flight checklist before promoting a beta build to production. Verifies the build holds up on real cars, not just your dev-server. The beta track gets your bundle in front of real testers. **Flight testing** is what you do *during* the beta — the systematic checks that catch issues your local dev-server can't see. Run this before running [`beta promote-production`](/docs/develop/beta-testing#promote-the-beta-to-production). > First time? Start at [Beta testing](/docs/develop/beta-testing) for > the publish + invite flow. This page is for the *post-invite, > pre-promotion* phase. ## When to run a flight test [#when-to-run-a-flight-test] Always, for any change touching: * Bridge calls (`getContext`, `callApi`, car-status subscriptions). * Manifest fields the host enforces (`safeWhileDriving`, `permissions`, `minHostVersion`, `url` origin). * `@i99dash/admin-sdk` calls — privileged ops have user-consent and session-cap dimensions you can't simulate locally. * Anything that runs while moving (the safety gate is a real input). Skip it for copy-only or CSS-only changes if you have screenshot diffs in CI. ## Pre-flight checklist (\~10 minutes per beta) [#pre-flight-checklist-10-minutes-per-beta] Print, paste in a checklist tool, or just skim — but don't skip the ones marked **(blocker)**. ### 1. Catalog presence [#1-catalog-presence] * [ ] **(blocker)** App appears under the tester's "Beta available" card on first launch. * [ ] Release notes from `--release-notes` render in the consent sheet, no truncation, locale-correct (test at least `en` and `ar`). * [ ] Icon + name match the manifest. No "default app" placeholder. ### 2. Safety gate [#2-safety-gate] * [ ] **(blocker)** With `safeWhileDriving: true`: app loads while car is moving (toggle dev-server's driving switch first to reproduce, then verify on a real car at >5 km/h). * [ ] With `safeWhileDriving: false` or unset: app shows the host's "not available while driving" card. * [ ] No crash, no white screen, no flicker on the moving → parked transition. ### 3. Context handling [#3-context-handling] * [ ] **(blocker)** Different VINs render different content (or explicit "no car bound" state). * [ ] Locale flip (`en` → `ar`) flips text direction and translations. No layout breakage. * [ ] `isDark` flip matches the host theme — no white flash on dark bring-up. ### 4. Network paths [#4-network-paths] * [ ] **(blocker)** Backend `callApi` paths are on the host's allow-list. A `disallowed_path` envelope means the manifest origin needs ops review. * [ ] Transient failures degrade gracefully — no infinite spinners. * [ ] Offline launch (airplane mode) shows the right empty state. ### 5. Subscriptions [#5-subscriptions] * [ ] **(blocker)** `onStatusChange` cleans up — observe in DevTools that disconnect/reconnect doesn't grow a sub count. * [ ] Background → foreground (lock-screen, app-switch) doesn't lose events; the SDK's catch-up replay fires once on resume. * [ ] Stale data (`status.staleness === 'very_stale'`) renders a visible "no signal" cue, not stale numbers as if fresh. ### 6. Privileged ops (admin-sdk only) [#6-privileged-ops-admin-sdk-only] * [ ] **(blocker)** First call after install triggers the consent sheet. Approving persists; subsequent calls don't re-prompt until expiry. * [ ] Tier-2 templates (`sys.reboot`, `pm.install`) trigger a *fresh* step-up prompt at action time even after install consent. * [ ] Session-cap expiry (90 days) is auto-refreshed; no manual user action required mid-session. * [ ] **`session_cap_expired`** envelopes resolve on a single retry after \~500 ms. ### 7. Performance [#7-performance] * [ ] **(blocker)** Cold start (first launch from a cleared cache) is \< 1.5 s on the head-unit hardware. Bundle size \< 1 MB uncompressed. * [ ] No memory growth across 10 minutes of typical use (DevTools → Memory → Heap snapshots before/after). * [ ] No console errors or `console.warn` spam from the SDK in dev mode. ### 8. Crash + recovery [#8-crash--recovery] * [ ] **(blocker)** Force-killing the host while your app is open doesn't leave the user in a broken state on relaunch. * [ ] A simulated bridge failure (`fakeBridge` with `throw`) lands the user on a fallback UI, not an unhandled-rejection toast. ## Flight-test the testers [#flight-test-the-testers] Beta-track features the SDK can't validate but you can: * [ ] **Tester roster reflects reality.** Run `beta testers ` after a few days; remove anyone whose status is still `invited` after 48 h (likely a typo'd Telegram username). * [ ] **At least 5 active testers.** Below that, you don't have meaningful coverage. Use `beta invite-batch`. * [ ] **Tester feedback channel exists.** A `beta` Telegram chat or Discord works; relying on issue creation alone undercounts the noise. ## Promotion criteria [#promotion-criteria] Don't promote to production until **all blockers pass for at least 3 days** and you've collected feedback from 3+ testers including: * 1 with a different car make / model than yours (cross-checks the car-status payload variance). * 1 on a different locale (RTL flip exposes layout bugs). * 1 who actually drives with the mini-app open (safety gate exercised in the wild). When all three boxes are green, run: ```bash sdk-i99dash beta promote-production com.example.myapp ``` The current beta bundle becomes the production version with no rebuild. Same SHA-256, same CDN URL, same `(id, version)` pair. ## After promotion [#after-promotion] Set a calendar reminder to check in 24 h after the promotion: * [ ] **Crash-free rate** isn't worse than the previous production build. * [ ] **`beta testers `** roster is unchanged — testers should have automatically rolled to the production build (the consent lifts on promotion). If anything regresses, you have a fast rollback: ```bash # Re-promote the prior version sdk-i99dash publish --track production --bundle prior-version-build/ ``` A new bundle SHA, but the user-visible content matches what was on production yesterday. ## Related [#related] * [Beta testing](/docs/develop/beta-testing) — the publish + invite * promote flow this checklist sits inside. * [Best practices](/docs/guides/best-practices) — the rules these blockers exist to catch the absence of. * [Subscriptions](/docs/guides/subscriptions) — debugging the cleanup blockers in the subscription section. * [Troubleshooting](/docs/troubleshooting) — every symptom we've seen in the wild, with root cause + fix. --- # Develop (/docs/develop) Working-on-your-app reference — local dev, auth, beta + flight test, privileged ops, MCP integration for AI agents. These pages are the "while building" section. Past Quickstart but before reading the API reference symbol-by-symbol. --- # Local development (/docs/develop/local-dev) Fixture grammar, control panel, and how to run the dev-server alongside your framework's own. The dev-server (`@i99dash/sdk-dev-server`, invoked via `sdk-i99dash dev`) simulates the i99dash host so your mini-app's code paths run **unchanged**. No `if (dev)` branches in your app. ## `sdk.config.json` [#sdkconfigjson] ```json { "appRoot": "./src", "distDir": "./dist", "mocksDir": "./mocks", "dev": { "port": 5173, "host": "127.0.0.1", "context": { "userId": "u-dev", "activeCarId": "VIN-DEV-0001", "locale": "en", "isDark": false } } } ``` Every field has a sensible default; the file is optional. ## Fixture grammar [#fixture-grammar] `mocks/fuel-stations.GET.json`: ```json { "match": { "path": "/api/v1/fuel-stations", "method": "GET" }, "response": { "success": true, "data": { "stations": [{ "name": "Shell", "price_sar": 2.33 }] } } } ``` To simulate an error: ```json { "match": { "path": "/api/v1/fuel-stations", "method": "GET", "query": { "simulate": "offline" } }, "response": { "success": false, "error": { "code": "NETWORK_ERROR", "message": "simulated" } } } ``` Matching rules (first filename alphabetically wins): 1. `path` exact. 2. `method` exact. 3. If `query` is present, every declared key/value must appear in the incoming request's query. Extra keys in the request are fine; missing keys are a miss. Missing fixture → `{success: false, error: {code: 'NO_FIXTURE', ...}}` so you always see gaps loudly during development. Fixtures hot-reload on save. ## Control panel [#control-panel] `http://127.0.0.1:5173/_sdk/ui`: The dev-server control panel with toggles for Driving, VIN, Locale, and Theme. Toggle: * **Driving** (off = 0 km/h, on = 40 km/h; exercises the real host's safety gate). * **VIN** (free-form text). * **Locale** (ar / en — flips text direction in the SDK's host context). * **Theme** (light / dark — matches the host's `isDark` field). All changes are live — the shim reads state from the dev-server, so calling `getContext` again returns the latest values. ## Gotchas [#gotchas] | Symptom | Fix | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `NotInsideHostError` thrown in a test / SSR | Use `MiniAppClient.withBridge(fake)` instead of `.fromWindow()`. | | Fixture changes don't reload | Confirm your editor saves in-place (not atomic-rename); chokidar picks up `change` events, but some editors write to a temp file then swap. | | `NO_FIXTURE` even though I have a file | File name must end `.json`; the `match.query` clauses must match. Verify via `http://127.0.0.1:5173/_sdk/ui`. | | Browser won't open | Pass `--no-open` to suppress the auto-open; visit the URL manually. | | Want to expose to a phone on the same Wi-Fi | `sdk-i99dash dev --host 0.0.0.0`. You'll get a warning — exposing a dev server publicly is a choice. | ## Running your framework's own dev-server alongside [#running-your-frameworks-own-dev-server-alongside] You can skip `sdk-i99dash dev` and run your framework's native dev server (e.g. `next dev`) — the catch is that **the host bridge isn't attached**. Two workarounds: 1. Add a ` ``` ### Svelte 5 (runes) [#svelte-5-runes] ```svelte ``` ### Vanilla — no framework [#vanilla--no-framework] ```ts const off = client.car.onStatusChange(render); window.addEventListener('beforeunload', off); // And anywhere you tear down your widget mid-page-life: // off(); ``` ## Page Visibility — automatic pause + catch-up [#page-visibility--automatic-pause--catch-up] The SDK installs a `document.visibilitychange` listener the first time you subscribe. While `document.hidden === true`: * Your callback is **suppressed** (doesn't fire). * The latest event is **buffered** as `_lastWhilePaused`. On `visibilitychange` back to visible: * ONE catch-up callback fires with the buffered status. * Normal flow resumes. You don't opt in or out; it just works. Result: a backgrounded mini-app costs near-zero CPU on car-status traffic, but renders correctly the moment the user returns. **Connection-state callbacks are NOT paused** — a backgrounded app still wants to know if the car signal went stale, so it can render the right banner the moment it becomes visible. ## Subscriber quota: ≤10 per mini-app [#subscriber-quota-10-per-mini-app] Every viewer enforces a per-mini-app cap of 10 concurrent subscriptions. The 11th throws `CarStatusQuotaExceededError`. If you hit this in production, you have a leak. The fix is **store the unsubscribe fn** instead of subscribing again: ```ts // ❌ Wrong — resubscribes on every keystroke; leaks 1 sub per render function Search({ client }) { const [q, setQ] = useState(''); client.car.onStatusChange(handler); // ← bug: leaks return setQ(e.target.value)} />; } // ✓ Right — useEffect-cleanup teardown; one sub per mount function Search({ client }) { const [q, setQ] = useState(''); useEffect(() => client.car.onStatusChange(handler), [client]); return setQ(e.target.value)} />; } ``` ## Throttling — not your problem [#throttling--not-your-problem] The host caps pushes to \~10 events / sec per mini-app via a token bucket. If the underlying car state changes faster than that (impossible in practice — the host polls at 10s), the bucket drops the **oldest queued** event in favour of the latest. So you always see the freshest state, never replayed history. Keep-alive events (one every \~30s when nothing changed) are exempt from the bucket so `onConnectionChange` stays observable on a parked car. You don't write any backpressure code. The host handles it. ## Connection state vs. staleness [#connection-state-vs-staleness] These are independent signals: | Signal | Source | What it means | When to use | | -------------------- | -------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------- | | `status.staleness` | Host clock vs. last poll | "How old is THIS payload?" (`fresh` \< 15 s, `stale` 15–60 s, `very_stale` > 60 s) | Greying-out an indicator that's no longer trustworthy | | `onConnectionChange` | Host's polling-loop health | "Has the host's read-loop succeeded recently?" | Showing a "no signal" banner | A parked car with healthy polling is `connected` + `fresh`. A daemon crash mid-session is `disconnected` + (eventually) `stale`/`very_stale`. Render with both — staleness for individual fields, connection state for the global banner. ## Errors [#errors] | Error | When | Fix | | ----------------------------- | ---------------------------------------------------------- | ----------------------------------------------------- | | `CarStatusUnavailableError` | Bridge isn't a `CarStatusBridge` (test stub or older host) | Catch + render a fallback UI | | `CarStatusQuotaExceededError` | >10 concurrent subscribers from this mini-app | Find the leak — see "Subscriber quota" above | | `InvalidResponseError` | Host pushed a malformed payload | Rare — usually a host/SDK version drift; log + report | The SDK silently drops malformed events (with a `console.warn` in dev) so a single bad payload doesn't throw inside your callback. ## Related [#related] * [Errors reference](/docs/api/sdk/errors) — every error code. * [Real-time car status widget recipe](/docs/recipes/car-status-widget) — full end-to-end example. * [Testing](/docs/guides/testing) — how to test these subscriptions. --- # Testing (/docs/guides/testing) How to test mini-app code with `MiniAppClient.withBridge()` — the one seam you need. `MiniAppClient.withBridge(...)` is the one seam you need. Hand it any object that satisfies the `Bridge` (or `CarStatusBridge`) interface and the rest of the SDK runs unchanged. No platform channels, no WebView, no fixtures. ## TL;DR — Vitest setup [#tldr--vitest-setup] ```ts // fuel-stations.test.ts import { describe, expect, it } from 'vitest'; import { MiniAppClient, type Bridge } from '@i99dash/sdk'; import { renderStations } from './fuel-stations'; describe('renderStations', () => { it('shows nearest first', async () => { const bridge: Bridge = { getContext: async () => ({ userId: 'u1', activeCarId: 'WDB1234567', locale: 'en', isDark: false, appVersion: '1.0.0', appId: 'fuel_prices', }), callApi: async (req) => { if (req.path === '/api/v1/fuel-stations') { return { success: true, data: { stations: [{ name: 'Aramco A', km: 0.4 }] }, }; } return { success: false, error: { code: 'disallowed_path', message: req.path } }; }, }; const client = MiniAppClient.withBridge(bridge); const html = await renderStations(client); expect(html).toContain('Aramco A'); }); }); ``` No `jsdom`, no `happy-dom` — `Bridge` is a plain TypeScript interface; the test runs in pure Node. ## Asserting bridge calls [#asserting-bridge-calls] For "did the page actually call this endpoint?" tests, capture calls in a small spy: ```ts import { vi } from 'vitest'; const callApi = vi.fn(async (req) => ({ success: true, data: null })); const bridge: Bridge = { getContext: async () => ctx, callApi, }; await render(MiniAppClient.withBridge(bridge)); expect(callApi).toHaveBeenCalledWith({ path: '/api/v1/fuel-stations', method: 'GET', query: { lat: 24.7, lng: 46.6 }, }); ``` Vitest's `vi.fn` (or Jest's `jest.fn`) gives you `mock.calls` for ad-hoc matchers; no need for a mock library. ## Covering error paths [#covering-error-paths] Each `SDKError` subclass is constructible — throw it from your fake bridge to drive the error branch: ```ts import { BridgeTransportError, BridgeTimeoutError } from '@i99dash/sdk'; const bridge: Bridge = { getContext: async () => { throw new BridgeTransportError('host crashed', new Error('underlying')); }, callApi: async () => ({ success: true, data: null }), }; await expect(client.getContext()).rejects.toBeInstanceOf(BridgeTransportError); ``` For protocol failures (`{success: false, error: {...}}`) — those are first-class data, not exceptions — return the failure envelope from `callApi` and assert on the value, not the throw. ## Testing `client.car.*` [#testing-clientcar] The car-status surface needs a `CarStatusBridge`. Capture the notify callback so the test can synchronously inject status events: ```ts import { describe, expect, it, vi } from 'vitest'; import { MiniAppClient, type CarStatusBridge } from '@i99dash/sdk'; let pushStatus: ((raw: unknown) => void) | undefined; const bridge: CarStatusBridge = { // Base Bridge: getContext: async () => ctx, callApi: async () => ({ success: true, data: null }), // Streaming surface: async getCarStatus() { return { vin: 'V', at: '2026-04-27T12:00:00Z', staleness: 'fresh', doorsLocked: true, }; }, async subscribeCarStatus(notify) { pushStatus = notify; return { id: 'sub-1' }; }, async unsubscribeCarStatus() { pushStatus = undefined; }, async subscribeCarConnectionState() { return { id: 'conn-1' }; }, async unsubscribeCarConnectionState() {}, }; it('updates the lock indicator on lock change', async () => { const client = MiniAppClient.withBridge(bridge); const cb = vi.fn(); client.car.onStatusChange(cb); // The lazy-subscribe is async — drain microtasks once. await Promise.resolve(); pushStatus?.({ vin: 'V', at: '2026-04-27T12:01:00Z', staleness: 'fresh', doorsLocked: false, }); expect(cb).toHaveBeenCalledOnce(); expect(cb.mock.calls[0][0].doorsLocked).toBe(false); }); ``` ## Testing privileged ops (`@i99dash/admin-sdk`) [#testing-privileged-ops-i99dashadmin-sdk] Use `FakeAdminBridge` — same idea, but for the admin surface: ```ts import { AdminClient, FakeAdminBridge, snapshotFromList } from '@i99dash/admin-sdk'; const bridge = new FakeAdminBridge(async (req) => { // Assert what your code sent the host: expect(req.templateId).toBe('diag.tail_logs'); expect(req.idempotencyKey).toBeTruthy(); return { success: true, data: { lines: ['boot ok'] } }; }); const admin = AdminClient.withBridge({ bridge, context: { appId: 'diagnostics-pro', vin: 'WDB1234567' }, catalog: snapshotFromList([/* CommandTemplate rows */]), }); await admin.tailLogs({ lines: 50 }); ``` For idempotency-key-replay tests, write the handler to track call counts and assert the second call with the same key short-circuits. ## What to test [#what-to-test] The SDK is well-tested on its own — what you should test in your mini-app is **your code's reaction to bridge results**: | Test case | What it catches | | -------------------------------------------- | ------------------------------------------------------------ | | Happy path: real data → render | Forgot to map a field, off-by-one in pagination | | `success: false` envelope | Forgot the failure branch — common production crash | | `BridgeTimeoutError` thrown | UI doesn't show a stuck spinner forever | | `NotInsideHostError` (SSR) | Server-rendered page doesn't crash on first paint | | Empty `activeCarId` (no car bound) | UI doesn't render with empty VIN; gates correctly | | Stale car status (`staleness: 'very_stale'`) | UI shows "no signal" instead of pretending it's live | | Subscription cleanup on unmount | Memory leak under React `useEffect` strict-mode double-mount | ## What NOT to test [#what-not-to-test] * **The SDK's schema validation** — that's covered by the SDK's own `__tests__/`. You don't need to assert that `getContext()` returns a `MiniAppContext`-typed value; the type system already promises that. * **The host's behaviour** — your mini-app ships a fake bridge in CI; the real host's contract is integration-tested in the host repo, not yours. * **Wire-format parsing** — the SDK does the JSON / zod work. ## Related [#related] * [`Bridge`](/docs/api/sdk/bridge) — full interface reference. * [Errors reference](/docs/api/sdk/errors) — every error class. * [Best practices](/docs/guides/best-practices) — handling these errors in production, not just tests. --- # Type-only imports (/docs/guides/type-only-imports) When to use `@i99dash/sdk/types` vs the main entry, and why both exist. Both runtime packages publish a type-only subpath. Use it when you need a SHAPE but never a RUNTIME. ```ts // Pulls the type only — emits zero JavaScript. import type { MiniAppContext, CarStatus } from '@i99dash/sdk/types'; import type { CommandTemplate } from '@i99dash/admin-sdk/types'; ``` The dist artifact for `/types` contains a single empty `.js` file (literally 40 bytes after minification — the ESM module marker). Tree-shakers drop it entirely. ## When you actually want this [#when-you-actually-want-this] | Scenario | Use `/types`? | | ----------------------------------------------------------- | ----------------------------------------- | | Server-rendered page that types a prop as `MiniAppContext` | ✓ Yes | | Shared schema package referenced by both mini-app + backend | ✓ Yes | | Pure-TypeScript helper that maps a `CarStatus` to a UI tree | ✓ Yes (works in Node, web worker, deno) | | Anything that calls `MiniAppClient.fromWindow()` | ✗ No — needs the runtime | | Tests that exercise the `Bridge` interface | ✗ No — needs the runtime for `withBridge` | ## Why it exists [#why-it-exists] * **SSR cleanliness.** Importing the runtime SDK from a Next.js server component pulls a dependency that calls `window` defensively. No actual error, but webpack lights up the SSR-incompatibility warning. `import type from '/types'` skips the whole branch. * **Bundle visibility.** A consumer who sees `import type { X } from '@i99dash/sdk/types'` knows by inspection that this code path doesn't add bytes to the runtime bundle. With a regular type-only import from `'@i99dash/sdk'`, you have to trust the bundler is doing dead-code-elimination correctly. * **Cross-package use without duplication.** Your shared `schemas` package can `import type { CarStatus }` from `/types` without pulling the rest of the SDK into its own dependency tree. ## What's exported from `/types` [#whats-exported-from-types] Same wire types you'd get from the main entry, plus the type aliases that go with them. Names match `@i99dash/sdk` exactly. `@i99dash/sdk/types`: ```ts type MiniAppContext; type CallApiRequest; type CallApiResponse; type ApiMethod; // 'GET' (v1) type CarStatus; type CarStatusStaleness; // 'fresh' | 'stale' | 'very_stale' type CarConnectionState; // 'connected' | 'disconnected' type CarDoors; type CarDoorState; // 'open' | 'closed' type CallOptions; type SDKErrorCode; type Bridge; type CarStatusBridge; type HostBridgeApi; type WindowWithHost; type CarStatusListener; type CarConnectionListener; ``` `@i99dash/admin-sdk/types`: ```ts type AdminClientContext; type AdminClientOptions; type InvokeOptions; type AdminBridge; type AdminExecRequest; type AdminOpResponse; type CapabilityResponse; type CatalogSnapshot; type CommandTemplate; type ParamRule; ``` ## Caveats [#caveats] * **No runtime symbols** are exported. If you write `import { CarStatusSchema } from '@i99dash/sdk/types'` (note: not `import type`), TypeScript will let you (the schema is in the source), but at runtime it'll be `undefined`. Use the main entry for schemas: `import { CarStatusSchema } from '@i99dash/sdk-types'` (the wire-types package). * **No re-exports.** `/types` is a flat list; there's no `@i99dash/sdk/types/car` subpath. Keeps the bundle audit simple. * **`enum` would be a runtime.** None of the type-only exports are `enum`s — they're string-literal unions or interfaces. Future schema additions follow the same rule. ## Related [#related] * [API reference](/docs/api) — full export listing with runtime counterparts. * [Best practices — Imports](/docs/guides/best-practices#imports) — convention for the rest of your imports. --- # Real-time car status widget (/docs/recipes/car-status-widget) End-to-end recipe — scaffold, wire the streaming car-status bridge, render door / battery / lock state, ship it. ~30 minutes. End-to-end: scaffold a mini-app, wire it to the host's car-status stream, render door / lock / battery state with live updates, ship it. \~30 minutes if you've never used the SDK. The finished widget: ``` ┌─────────────────────────────┐ │ Car: ****4567 🔒 │ │ Battery: 88% │ │ Doors: 🚪 driver open │ │ Last seen: 3 s ago │ └─────────────────────────────┘ ``` The widget shows live state and degrades cleanly when the host has no signal. It uses every relevant SDK feature: `getContext`, `client.car.getStatus`, `onStatusChange`, `onConnectionChange`, proper cleanup, and error handling for the offline / no-host case. ## Prerequisites [#prerequisites] * Node 20+ * pnpm (or npm; the recipe uses pnpm) * 10 min of patience for the dev-server's first launch ## 1. Scaffold [#1-scaffold] ```bash npx @i99dash/sdk-cli init car-status-widget cd car-status-widget pnpm install ``` The CLI creates a vanilla TS template (`src/main.ts`, `src/index.html`, `manifest.json`). If you want React / Vue, `--framework react` or `--framework vue`. Recipe stays vanilla so the SDK calls are visible. ## 2. Bump `minHostVersion` for the streaming feature [#2-bump-minhostversion-for-the-streaming-feature] The car-status stream lives in host versions ≥ `0.0.2`. Edit your generated `manifest.json`: ```json { "id": "car_status_widget", "name": { "en": "Car Status", "ar": "حالة السيارة" }, "minHostVersion": "0.0.2", "permissions": ["car.status.read"], "safeWhileDriving": true } ``` `permissions` defaults to `['car.status.read']` if omitted (v1 backward compat) but make it explicit so future you can grep for the contract. `safeWhileDriving: true` lets the host show the widget while moving — appropriate for a read-only status display. ## 3. Wire the SDK [#3-wire-the-sdk] Edit `src/main.ts`: ```ts import { MiniAppClient, SDKError, type CarStatus, type CarConnectionState, } from '@i99dash/sdk'; const client = MiniAppClient.fromWindow(); const root = document.querySelector('#root')!; let status: CarStatus | null = null; let conn: CarConnectionState = 'disconnected'; function render() { if (conn === 'disconnected') { root.innerHTML = ``; return; } if (status === null) { root.innerHTML = `

Loading…

`; return; } const vinTail = `****${status.vin.slice(-4)}`; const lock = status.doorsLocked ? '🔒' : '🔓'; const battery = status.batteryPct ?? '—'; const driver = status.doors?.driver ?? 'unknown'; const stale = status.staleness === 'fresh' ? '' : ` (${status.staleness})`; root.innerHTML = `
Car: ${vinTail} ${lock}
Battery: ${battery}%
Doors: 🚪 driver ${driver}
Last seen: ${ageLabel(status.at)}${stale}
`; } function ageLabel(iso: string): string { const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000); if (seconds < 5) return 'just now'; if (seconds < 60) return `${seconds} s ago`; return `${Math.round(seconds / 60)} min ago`; } // One-shot first paint — no waiting for the first delta. async function bootstrap() { try { status = await client.car.getStatus(); } catch (e) { if (e instanceof SDKError && e.code === 'CAR_STATUS_UNAVAILABLE') { // Older host or test bridge — render a fallback. root.innerHTML = `

This car doesn't support live status.

`; return; } throw e; } render(); // Subscribe to deltas — store the unsub fn for cleanup. const offStatus = client.car.onStatusChange((next) => { status = next; render(); }); const offConn = client.car.onConnectionChange((next) => { conn = next; render(); }); // Tear down on tab close (the host doesn't kill us automatically // when the user navigates away inside our shell). window.addEventListener('beforeunload', () => { offStatus(); offConn(); }); } bootstrap(); ``` A quick read-through of what's here: * **`MiniAppClient.fromWindow()`** — finds the host bridge. In dev, the bridge comes from `sdk-i99dash dev`. * **`client.car.getStatus()`** — one-shot read for the first paint. Avoid showing a loading spinner forever waiting for the first delta event. * **`SDKError` switch on `code`** — catches the "older host doesn't support streaming" path. Renders a fallback instead of crashing. * **`onStatusChange` / `onConnectionChange`** — return an unsubscribe fn each. Both are stored so `beforeunload` can clean them up. * **`status.staleness`** — gates the "live or stale" label separately from the connection-state banner. See [Subscriptions — Connection state vs. staleness](/docs/guides/subscriptions#connection-state-vs-staleness). * **VIN masking** — only the last 4 digits render. See [Best practices — Privacy](/docs/guides/best-practices#privacy). ## 4. Run it locally [#4-run-it-locally] ```bash pnpm dev ``` The CLI launches `sdk-dev-server` and opens your default browser at `http://localhost:5173` with the dev-server's bridge shim pre-installed. You'll see a fake "parked, doors locked, 88% battery" status because the dev-server's default `CarStatus` fixture says so. To exercise live updates, open the dev-server's UI at `http://localhost:5174/_dev` and toggle the door / lock / moving state. Each change drives an `onStatusChange` callback in your widget within \~100 ms. ## 5. Test the cleanup path [#5-test-the-cleanup-path] Add a Vitest test: ```ts // src/main.test.ts import { describe, expect, it, vi } from 'vitest'; import { MiniAppClient, type CarStatusBridge } from '@i99dash/sdk'; describe('car-status widget', () => { it('cleans up subscriptions on beforeunload', async () => { const subscribed = vi.fn(async (notify) => { void notify; return { id: 'sub-1' }; }); const unsubscribed = vi.fn(async () => {}); const bridge: CarStatusBridge = { getContext: async () => ({ userId: 'u', activeCarId: 'V', locale: 'en', isDark: false, appVersion: '1.0.0', appId: 'car_status_widget', }), callApi: async () => ({ success: true, data: null }), getCarStatus: async () => ({ vin: 'V', at: '2026-04-27T12:00:00Z', staleness: 'fresh', }), subscribeCarStatus: subscribed, unsubscribeCarStatus: unsubscribed, subscribeCarConnectionState: async () => ({ id: 'conn-1' }), unsubscribeCarConnectionState: async () => {}, }; const client = MiniAppClient.withBridge(bridge); const off = client.car.onStatusChange(() => {}); await Promise.resolve(); // drain the lazy subscribe microtask expect(subscribed).toHaveBeenCalledOnce(); off(); await Promise.resolve(); expect(unsubscribed).toHaveBeenCalledOnce(); }); }); ``` ```bash pnpm test ``` If `unsubscribed` is never called, your code has a leak — the same class of bug that throws `CarStatusQuotaExceededError` in production. See [Subscriptions](/docs/guides/subscriptions#subscriber-quota-10-per-mini-app). ## 6. Validate the manifest + build [#6-validate-the-manifest--build] ```bash pnpm sdk-i99dash validate # zod-checks manifest.json pnpm sdk-i99dash build # produces dist/bundle.tar.gz ``` The validate step catches missing `minHostVersion`, malformed permissions, and (in this case) reminds you that `safeWhileDriving: true` requires explicit opt-in justification in your PR. ## 7. Publish [#7-publish] ```bash pnpm sdk-i99dash login # device-code flow if not already logged in pnpm sdk-i99dash publish ``` The catalog picks up the new version on next refresh; users with host ≥ 0.0.2 see your widget tile in the store. ## What you didn't have to write [#what-you-didnt-have-to-write] * **Throttling.** The host token-buckets pushes at \~10/sec; you don't need to debounce. * **Page Visibility handling.** The SDK suppresses callbacks while hidden and fires one catch-up on resume. Your `render()` doesn't thrash when the user backgrounds the tab. * **Schema validation.** Every event the host pushes is zod-validated by the SDK. A malformed payload silently drops with a `console.warn` in dev. * **Reconnect logic.** `onConnectionChange` reflects the host's polling-loop health; banner updates automatically when the signal comes back. ## Troubleshooting [#troubleshooting] | Symptom | Likely cause + fix | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | "This car doesn't support live status." | You're on an older host. Bump `minHostVersion` in the manifest so the catalog hides your app from incompatible cars. | | Widget never updates | Check `pnpm dev`'s console for `console.warn` from the SDK — the host may be pushing a malformed payload. Otherwise, check that `onStatusChange` is wired before `bootstrap` returns. | | `CarStatusQuotaExceededError` in dev | You're calling `onStatusChange` on every render (likely React strict mode). Wrap in `useEffect` and return the off fn. See [Subscriptions](/docs/guides/subscriptions). | | `NotInsideHostError` in browser | Opened the bundle directly via `file://`. Run through `pnpm dev` so the dev-server attaches the bridge. | ## Related [#related] * [`getStatus` reference](/docs/api/sdk/car-status-controller) — full API reference. * [Subscriptions](/docs/guides/subscriptions) — full lifecycle deep-dive. * [Testing](/docs/guides/testing) — more test patterns. * [Troubleshooting](/docs/troubleshooting) — known SDK gotchas. --- # Fetch and render a list (/docs/recipes/fuel-prices) The simplest real recipe — call your backend once, render a list, handle the failure path. ~10 minutes. This is the recipe most developers want first: call a backend, get JSON back, render it. We'll build a tiny "fuel stations near me" list. \~10 minutes if you've done the [Quickstart](/docs/getting-started). The finished widget: ``` ┌──────────────────────────────────────┐ │ Fuel near you │ │ ──────────────────────────────── │ │ Aramco — Olaya Rd 2.33 SAR │ │ Shell — King Fahd Rd 2.34 SAR │ │ ADNOC — Tahlia St 2.35 SAR │ │ │ │ 3 stations · updated just now │ └──────────────────────────────────────┘ ``` What you'll exercise: * `MiniAppClient.fromWindow()` — the client. * `client.callApi()` — backend proxy. * The success / error envelope branching. * Loading + empty + error states. * A fixture file so the dev-server has something to return. ## Prerequisites [#prerequisites] * Node 20 +, pnpm 9 +. * \~10 minutes. ## Before you start: where does the backend live? [#before-you-start-where-does-the-backend-live] The most-asked question on this recipe. In the call ```ts client.callApi({ path: '/api/v1/fuel-stations', method: 'GET' }); ``` `/api/v1/fuel-stations` is **not** a URL — it's a **route key** the host maps to one of two things, depending on environment: | Where you run | What backs `/api/v1/fuel-stations` | | ---------------------- | ------------------------------------------------------------------------------------------------------------------- | | Local dev (`pnpm dev`) | A JSON fixture in `mocks/*.json`. No network call. | | Production | **Your own** HTTPS service, mapped path-by-path in the host's allow-list. Configured by i99dash ops at deploy time. | There is **no shared platform backend** for mini-apps to call for free — the i99dash platform's own API is internal infrastructure, not a developer surface. Every production `callApi` path resolves to a service you own and run. (For dev, the `mocks/` fixture *is* the backend, so you can finish this recipe without standing anything up.) The wiring for your real backend is at the bottom of this page; it's a one-time coordination step with ops. ## 1 — Scaffold [#1--scaffold] ```bash pnpm dlx @i99dash/sdk-cli init fuel-list --template vanilla cd fuel-list pnpm install ``` The vanilla template gives you `src/index.html`, `src/main.ts`, `manifest.json`, and an example fixture in `mocks/`. ## 2 — Replace `src/main.ts` [#2--replace-srcmaints] ```ts import { MiniAppClient, type MiniAppContext } from '@i99dash/sdk'; type Station = { name: string; road: string; price_sar: number }; const root = document.querySelector('#root')!; const client = MiniAppClient.fromWindow(); void render(); async function render() { // 1 — show a loading state up front so the user sees something. root.innerHTML = '

Loading stations…

'; // 2 — pull context once. Used for the "near you" wording + the // direction flip on Arabic locale. let ctx: MiniAppContext; try { ctx = await client.getContext(); } catch (e) { return renderError(`Couldn't read your context. ${describe(e)}`); } document.documentElement.lang = ctx.locale; document.documentElement.dir = ctx.locale === 'ar' ? 'rtl' : 'ltr'; // 3 — fetch the stations. Note the envelope branch. const r = await client.callApi<{ stations: Station[] }>({ path: '/api/v1/fuel-stations', method: 'GET', query: { vin: ctx.activeCarId }, }); if (!r.success) { return renderError( r.error.code === 'disallowed_path' ? `Backend not on the allow-list (${r.error.code}). Check ops.` : r.error.message, ); } // 4 — empty state vs list. if (r.data.stations.length === 0) { root.innerHTML = '

No stations near this VIN.

'; return; } root.innerHTML = `

${ctx.locale === 'ar' ? 'الوقود قريب منك' : 'Fuel near you'}

    ${r.data.stations .map( (s) => `
  • ${escape(s.name)} ${escape(s.road)} ${s.price_sar.toFixed(2)} SAR
  • `, ) .join('')}
${r.data.stations.length} stations · updated just now
`; } function renderError(msg: string) { root.innerHTML = `

${escape(msg)}

`; } function describe(e: unknown): string { return e instanceof Error ? e.message : String(e); } function escape(s: string): string { return s.replace( /[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!, ); } ``` A few things worth noticing: * **Loading first.** `render()` paints a loading state synchronously before any async work. The user never sees an empty page. * **`getContext` is wrapped in try/catch**, but `callApi` is not. That's deliberate: `getContext` only throws on real environment problems (no host, malformed schema). `callApi` returns its failures as data, so we branch on `r.success`. * **`disallowed_path` gets a specific message.** It's a config bug, not a user bug — distinguish in the UI so support tickets are actionable. * **No `try/catch` around `escape()`**, but we do call it on every string we interpolate. Bundles are public; XSS via a backend that returned ` ``` ## 6 — Run it [#6--run-it] ```bash pnpm dev ``` Open `http://127.0.0.1:5173` — three stations render. Hit `/_sdk/ui` and switch the locale to `ar` — the `` flips to `rtl` and the heading changes. To exercise the error path: visit `http://127.0.0.1:5173?simulate=disallowed` and confirm the error message ends up where you expect. ## Going to production with your own backend [#going-to-production-with-your-own-backend] The fixture is enough to *ship the bundle* — `pnpm publish` doesn't care about the backend. But for the published app to actually return real data on a user's car, the host needs to know where to forward each path. Here's the wiring. ### Step 1 — stand up the service [#step-1--stand-up-the-service] Anything that speaks HTTPS works. The contract you have to honour is the `CallApiResponse` envelope: ```ts // success { "success": true, "data": } // failure { "success": false, "error": { "code": "", "message": "" } } ``` A minimal Express/Fastify/Hono service that returns the same shape the fixture does: ```ts // server.ts (Hono on Bun / Node) import { Hono } from 'hono'; const app = new Hono(); app.get('/api/v1/fuel-stations', (c) => { const vin = c.req.query('vin'); return c.json({ success: true, data: { stations: [ { name: 'Aramco', road: 'Olaya Rd', price_sar: 2.33 }, { name: 'Shell', road: 'King Fahd Rd', price_sar: 2.34 }, { name: 'ADNOC', road: 'Tahlia St', price_sar: 2.35 }, ], }, }); }); export default app; ``` Deploy on whatever you like (DO App Platform, Vercel, Fly, Cloudflare Workers). Get an HTTPS URL like `https://fuel.example.com`. ### Step 2 — declare the path in your manifest [#step-2--declare-the-path-in-your-manifest] Your `manifest.json` doesn't list the *URL* — it lists the **path prefix** your app needs: ```json { "id": "fuel-list", "name": { "en": "Fuel List" }, "url": "https://your-cdn.example.com/fuel-list/", "version": "0.1.0", "category": "info", "permissions": [ "callApi:/api/v1/fuel-stations" ] } ``` The host honours `permissions` declarations at install time — users see "this app wants to call `/api/v1/fuel-stations`" in the consent sheet. Without the permission, the host returns `{ success: false, error: { code: 'disallowed_path' } }` for that path. ### Step 3 — coordinate the routing [#step-3--coordinate-the-routing] Send i99dash ops: * The path prefix(es) you declared (`/api/v1/fuel-stations`). * The HTTPS URL of your service (`https://fuel.example.com`). * Auth: how should the host pass the user's identity? Two options: * **Bearer-token forwarding.** The host sends a short-lived JWT in `Authorization: Bearer ...` that your backend verifies via the i99dash JWKS endpoint. Default and recommended. * **mTLS / shared secret.** For internal/enterprise backends. Ops will provision the cert. Ops adds an entry like: ```yaml # host's allow-list (managed centrally; not in your repo) - pathPrefix: /api/v1/fuel-stations upstream: https://fuel.example.com forwardAuth: jwt ownerApp: fuel-list ``` After this lands in production config, your published mini-app starts hitting the real backend on next launch. No app rebuild needed — `path` resolution is host-side. ### Step 4 — verify the wire [#step-4--verify-the-wire] The host attaches a short-lived JWT for the user's session in the `Authorization` header. Verify it on your backend before trusting the user identity. Ops will give you the **JWKS URL**, **issuer**, and **audience** values to use: ```ts import { jwtVerify, createRemoteJWKSet } from 'jose'; // Values supplied by ops when they add your app to the routing config. const JWKS_URL = process.env.I99DASH_JWKS_URL!; const ISSUER = process.env.I99DASH_ISSUER!; const AUDIENCE = 'fuel-list'; // your manifest id const JWKS = createRemoteJWKSet(new URL(JWKS_URL)); app.get('/api/v1/fuel-stations', async (c) => { const auth = c.req.header('Authorization'); if (!auth?.startsWith('Bearer ')) { return c.json( { success: false, error: { code: 'unauthorized', message: 'missing bearer' } }, 401, ); } const { payload } = await jwtVerify(auth.slice(7), JWKS, { issuer: ISSUER, audience: AUDIENCE, }); // payload.sub = userId, payload.vin = activeCarId, payload.locale = ... // ... }); ``` Now when your mini-app's `callApi` hits the host, the host attaches a JWT signed for *this user's session in your specific app*, and your backend can trust the user identity without the mini-app needing to manage tokens. ### Step 5 — test the production path locally [#step-5--test-the-production-path-locally] You don't need to wait for the host to route prod traffic to your service. Two patterns: * **Continue using fixtures** for unit/integration tests. Fast, deterministic, no network. * **Point the dev-server at your real backend** by writing a fixture that proxies — there's a `passthroughUrl` option in `sdk.config.json` for that. See [Local development](/docs/develop/local-dev). Once the production deployment is wired (step 3), publish your mini-app and traffic flows to your service automatically. ## What you didn't have to do [#what-you-didnt-have-to-do] * **Auth.** The host injects the user's session token; you don't manage credentials. * **CORS.** The host is the network egress; CORS is its problem. * **Origin handling.** Your `path` is host-relative — the allow-list decides where it resolves. * **Schema validation of the envelope.** `callApi` validates `{ success, data | error }` shape via zod before returning. A malformed payload throws `InvalidResponseError`, which you can treat as "host bug" and report. ## Where to go next [#where-to-go-next] * [Calling your backend](/docs/concepts/calling-your-backend) — the full mental model for `callApi`. * [Real-time car status widget](/docs/recipes/car-status-widget) — same shape, but with streaming subscriptions. * [Best practices](/docs/guides/best-practices) — production patterns for the failure paths you just wired up. * [Testing](/docs/guides/testing) — how to put this in CI without the dev-server. --- # Recipes (/docs/recipes) Copy-paste end-to-end builds and patterns. Each recipe is a full, runnable example. Recipes are runnable. Copy, adapt, ship. ## Start here [#start-here] ## Patterns [#patterns] ## Full builds [#full-builds] More coming as we hit common patterns. Got a request? Open an issue at [i99dash/sdk-i99dash](https://github.com/i99dash/sdk-i99dash/issues/new). --- # Locale-aware rendering (/docs/recipes/locale-aware) Read the host's locale, flip RTL, render translations, react to mid-session locale changes. ~5 minutes. The i99dash host runs in `en` or `ar`. Arabic flips the page direction to RTL — and gets it wrong if you forget. This recipe is the smallest correct version: read the locale, set `lang` + `dir`, render the right strings, refresh on change. ## Step 1 — read locale once on mount [#step-1--read-locale-once-on-mount] ```ts import { MiniAppClient } from '@i99dash/sdk'; const client = MiniAppClient.fromWindow(); const ctx = await client.getContext(); document.documentElement.lang = ctx.locale; document.documentElement.dir = ctx.locale === 'ar' ? 'rtl' : 'ltr'; ``` Setting `lang` and `dir` on `` cascades: * CSS `[dir="rtl"]` selectors flip layout. * Logical properties (`margin-inline-start`, `padding-inline-end`) resolve correctly. * `` text alignment matches the language without any code. ## Step 2 — translation table [#step-2--translation-table] ```ts const STRINGS = { en: { title: 'Fuel near you', loading: 'Loading…', empty: 'No stations.', retry: 'Retry', }, ar: { title: 'الوقود قريب منك', loading: 'جارٍ التحميل…', empty: 'لا توجد محطات.', retry: 'إعادة المحاولة', }, } as const; type Locale = keyof typeof STRINGS; export function t(locale: Locale, key: keyof typeof STRINGS.en): string { return STRINGS[locale][key] ?? STRINGS.en[key]; } ``` Why a flat table not `i18next` / `react-intl` for this size: bundles ship to constrained head-units. A 50-key flat table is \< 1 KB; a proper i18n library is 30–200 KB. Use the library only when you have hundreds of strings and pluralisation rules. ## Step 3 — react to mid-session locale changes [#step-3--react-to-mid-session-locale-changes] Currently, `getContext()` is the only way to read locale, and the host doesn't push an event when the user changes it system-wide. The next launch picks up the new value. For dev-mode iteration: the dev-server's control panel (`http://127.0.0.1:5173/_sdk/ui`) lets you flip locale live. Your code re-reads `getContext()` on the next call, so a refresh shows the updated language. ```ts async function refreshLocale() { const ctx = await client.getContext(); document.documentElement.lang = ctx.locale; document.documentElement.dir = ctx.locale === 'ar' ? 'rtl' : 'ltr'; rerender(ctx.locale); } // Re-read on every visibility change (cheap; one bridge call) document.addEventListener('visibilitychange', () => { if (!document.hidden) void refreshLocale(); }); ``` ## Step 4 — RTL-friendly CSS [#step-4--rtl-friendly-css] Logical properties make almost everything flip automatically: ```css .station { /* ❌ Will not flip in RTL */ /* margin-left: 12px; padding-right: 8px; text-align: left; */ /* ✅ Flips in RTL */ margin-inline-start: 12px; padding-inline-end: 8px; text-align: start; } ``` Use direction-aware utilities for icons and arrows: ```css .chevron { /* ❌ Always points right */ /* transform: rotate(0); */ /* ✅ Points "forward" in both languages */ transform: rotate(0); } [dir="rtl"] .chevron { transform: rotate(180deg); } ``` Or with logical properties: ```css .chevron::after { content: "›"; /* unicode handles direction in fonts that support it */ } ``` Tailwind shortcut: `ltr:` / `rtl:` variants for things logical properties can't express: ```html ``` ## Step 5 — number, date, currency [#step-5--number-date-currency] Always use the platform `Intl` APIs with the locale you read from `getContext()`. They handle digit shaping (Latin vs Arabic-Indic), decimal separator (`.` vs `٫`), and direction: ```ts const fmt = new Intl.NumberFormat(ctx.locale, { style: 'currency', currency: 'SAR', minimumFractionDigits: 2, }); fmt.format(2.33); // en: "SAR 2.33" // ar: "٢٫٣٣ ر.س.‏" ``` ## Common mistakes [#common-mistakes] | Mistake | Why it bites | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Using `margin-left` instead of `margin-inline-start` | Layout doesn't flip in RTL; site looks broken on Arabic | | Hard-coding `text-align: left` | Same — text goes the wrong way | | Forgetting `dir` attribute on `` | Browser doesn't know the direction; logical properties fall back to LTR | | Concatenating translated strings | `t('greet') + ' ' + name` works in English but Arabic word order is different. Use template strings with positional placeholders, or a real i18n library | | Using `toLocaleString()` without locale | Defaults to runtime locale, which may differ from the host's UI locale | ## Test it [#test-it] Open the dev-server's control panel (`http://127.0.0.1:5173/_sdk/ui`), flip Locale: `en` → `ar`, refresh your page. Verify: * [ ] `` is now `rtl`. * [ ] Layout flipped (text on the right, icons mirrored). * [ ] Numbers render with Arabic digits (`٢` not `2`) where the currency style includes them. * [ ] No left/right wording in copy ("see below" beats "see right" — "right" doesn't translate predictably). ## Related [#related] * [`MiniAppContext.locale`](/docs/api/types/mini-app-context) — the field on the schema. * [Best practices — privacy](/docs/guides/best-practices#privacy) — for user-id handling alongside locale. * [MDN: CSS logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) — exhaustive reference. --- # Polling with cancellation (/docs/recipes/polling) Refresh data on a timer, cancel cleanly when the user navigates away. ~5 minutes. You want a value that refreshes every N seconds. The naïve `setInterval` approach leaks subscriptions and stacks up requests when the network is slow. This recipe is the version that doesn't. ## What you'll exercise [#what-youll-exercise] * Promise-based polling loop (no `setInterval`) * `AbortController` cancellation, propagated to `callApi` * Page-Visibility-aware backoff (no work while the tab is hidden) * Cleanup that's safe to call twice ## The pattern [#the-pattern] ```ts import { MiniAppClient, type CallApiResponse } from '@i99dash/sdk'; interface PollOptions { intervalMs: number; // refresh cadence; 5_000 is a good default maxBackoffMs?: number; // cap on exponential backoff after errors } /// Poll `path` every `intervalMs`. Returns a stop fn. /// `onUpdate` fires on every successful response; `onError` fires on /// failed envelopes AND thrown bridge errors. export function poll( client: MiniAppClient, path: string, opts: PollOptions, onUpdate: (data: T) => void, onError?: (e: { code: string; message: string }) => void, ): () => void { const ctl = new AbortController(); let backoff = opts.intervalMs; const max = opts.maxBackoffMs ?? 60_000; void (async () => { while (!ctl.signal.aborted) { // Skip work while hidden. Wake-up on visibility change does // a fresh poll immediately. if (typeof document !== 'undefined' && document.hidden) { await waitForVisible(ctl.signal); if (ctl.signal.aborted) break; } try { const r = await client.callApi( { path, method: 'GET' }, { signal: ctl.signal }, ); if (r.success) { onUpdate(r.data); backoff = opts.intervalMs; // reset on success } else { onError?.(r.error); backoff = Math.min(backoff * 2, max); // back off on failure } } catch (e) { if (ctl.signal.aborted) break; // user-cancelled; stop loop onError?.({ code: 'transport', message: e instanceof Error ? e.message : String(e), }); backoff = Math.min(backoff * 2, max); } // Sleep until the next tick OR a stop signal. await sleep(backoff, ctl.signal); } })(); return () => ctl.abort(); } async function sleep(ms: number, signal: AbortSignal): Promise { if (signal.aborted) return; await new Promise((resolve) => { const id = setTimeout(resolve, ms); signal.addEventListener('abort', () => { clearTimeout(id); resolve(); }, { once: true }); }); } async function waitForVisible(signal: AbortSignal): Promise { if (signal.aborted) return; if (typeof document === 'undefined' || !document.hidden) return; await new Promise((resolve) => { const handler = () => { if (!document.hidden) { document.removeEventListener('visibilitychange', handler); resolve(); } }; signal.addEventListener('abort', () => { document.removeEventListener('visibilitychange', handler); resolve(); }, { once: true }); document.addEventListener('visibilitychange', handler); }); } ``` ## Use it [#use-it] ```ts const client = MiniAppClient.fromWindow(); const stop = poll<{ stations: Station[] }>( client, '/api/v1/fuel-stations', { intervalMs: 5_000 }, (data) => render(data.stations), (err) => showBanner(err.message), ); // On unmount / navigation: stop(); ``` In React, wrap with `useEffect`: ```tsx useEffect(() => { const stop = poll(client, '/api/v1/fuel-stations', { intervalMs: 5_000 }, setData); return stop; }, [client]); ``` ## Why this and not `setInterval`? [#why-this-and-not-setinterval] | `setInterval(fn, 5_000)` | This pattern | | --------------------------------------------------------------------- | ---------------------------------------------------------- | | Stacks up requests if the previous one hasn't returned (network slow) | Awaits each request before sleeping; never overlaps | | Runs while the tab is hidden, burning bandwidth | Pauses on `document.hidden`; resumes immediately on return | | Constant cadence even after errors | Exponential backoff up to `maxBackoffMs` on failure | | Hard to cancel mid-request | `AbortController` propagates into the in-flight `callApi` | | Cleanup races with in-flight callbacks | `signal.aborted` checked at every yield point | ## Edge cases [#edge-cases] * **Stop called during an in-flight call** — the `AbortController` rejects the SDK's `withTimeout` wrapper with an `AbortError`. The loop catches it, sees `signal.aborted`, exits cleanly. No late `onUpdate` fires. * **Tab hidden mid-request** — the in-flight call completes (the host doesn't pause its end). The result is delivered. Then the loop notices `document.hidden` and waits. * **Errored backoff** — successful response resets backoff to `intervalMs`. If the backend is flaky, you get a sane recovery curve instead of permanently degraded cadence. ## Related [#related] * [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) — DOM standard. * [Calling your backend](/docs/concepts/calling-your-backend) — `callApi`'s signal/timeout contract. * [Subscriptions](/docs/guides/subscriptions) — for *push* updates instead of polling, prefer this. * [Best practices](/docs/guides/best-practices) — performance + cleanup rules. --- # Troubleshooting (/docs/troubleshooting) Issues we've actually hit, with root causes and fixes. Symptom → cause → fix. If you hit something not here, [open an issue](https://github.com/i99dash/sdk-i99dash/issues/new). ## Install / scaffold [#install--scaffold] ### `ERR_PNPM_DLX_MULTIPLE_BINS` on `pnpm dlx @i99dash/sdk-cli` [#err_pnpm_dlx_multiple_bins-on-pnpm-dlx-i99dashsdk-cli] **Symptom:** ``` ERR_PNPM_DLX_MULTIPLE_BINS Could not determine executable to run. @i99dash/sdk-cli has multiple binaries: sdk-i99dash, i99dash ``` **Root cause:** `@i99dash/sdk-cli` versions ≤ 0.1.5 declared two binary entries (`sdk-i99dash` and `i99dash`). pnpm's `dlx` refuses to guess which to run when there are multiple. **Fix** (any of these): ```bash # 1. Upgrade — 0.1.6 ships only `sdk-i99dash`, dlx works again pnpm dlx @i99dash/sdk-cli@latest init my-app # 2. Pin to the multi-bin version with explicit bin selection pnpm --package=@i99dash/sdk-cli dlx sdk-i99dash init my-app # 3. Use npx, which doesn't have the multi-bin guard npx -y @i99dash/sdk-cli init my-app ``` After install, `pnpm exec sdk-i99dash ` works in all versions. ### `ERR_PNPM_FETCH_404` on `pnpm dlx @i99dash/sdk-cli` [#err_pnpm_fetch_404-on-pnpm-dlx-i99dashsdk-cli] **Symptom**: immediately after a fresh release of the CLI, `pnpm dlx` errors: ``` ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/@i99dash%2Fsdk-cli: Not Found - 404 @i99dash/sdk-cli is not in the npm registry, or you have no permission to fetch it. ``` But `curl https://registry.npmjs.org/@i99dash/sdk-cli` returns `200`, and `npm` works fine. **Root cause**: a known interaction between two layers: 1. **npm registry CDN propagation**: after a publish, the *manifest* endpoint (`//`) takes 5–30 minutes to replicate across CDN regions. The *version-specific* endpoint (`///`) is consistent immediately, but `pnpm dlx` resolves the latest version via the manifest endpoint first. 2. **pnpm's resolution cache**: when pnpm gets a 404 from the manifest endpoint, it caches the result for the rest of the dlx invocation. Even when the CDN catches up seconds later, `pnpm dlx` keeps returning the stale 404 until the cache is cleared. **Fix** (any of these): ```bash # Easiest — npx fetches the manifest directly without pnpm's cache npx -y @i99dash/sdk-cli init my-app # Or — clear pnpm's metadata cache + retry pnpm store prune pnpm dlx @i99dash/sdk-cli init my-app # Or — pin to the exact version (still hits manifest, but clearer error) pnpm dlx @i99dash/sdk-cli@0.1.2 init my-app ``` `pnpm install` of `@i99dash/sdk-cli` *inside an existing project* is unaffected — only `pnpm dlx` triggers the bug. ### DNS / `ENOTFOUND` on first `sdk-i99dash login` [#dns--enotfound-on-first-sdk-i99dash-login] **Symptom**: login attempt fails immediately with a DNS resolution error pointing at a stale backend hostname. **Root cause**: you're on `@i99dash/sdk-cli ≤ 0.1.1`. That version pinned a backend URL that no longer resolves; the URL was changed in `0.1.2`. **Fix**: upgrade. ```bash pnpm add -D @i99dash/sdk-cli@latest ``` Or, until you upgrade, override per-command — ops will give you the current backend URL: ```bash sdk-i99dash login --backend-url # or set the env var I99DASH_BACKEND_URL= sdk-i99dash login ``` ### `Command "sdk-i99dash" not found` [#command-sdk-i99dash-not-found] **Symptom**: `pnpm exec sdk-i99dash ` errors with `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL`. **Root cause**: the CLI is a devDependency, so the `sdk-i99dash` binary lives in `node_modules/.bin/`. If `node_modules/` is missing (you deleted it, moved the project without it, or skipped `pnpm install`), the binary isn't there. **Fix**: ```bash pnpm install pnpm exec sdk-i99dash --version ``` If you moved the project to a fresh directory, **always** re-run `pnpm install` — pnpm symlinks per-project. ## Login [#login] ### Browser opens but the verification page is 404 [#browser-opens-but-the-verification-page-is-404] **Symptom**: `sdk-i99dash login` opens an OAuth device-code URL and the browser shows 404. **Root cause**: the i99dash backend has `enable_sdk=false`. The OAuth device-code endpoints (and the entire `/api/v1/mini-apps/*` surface) are gated on the SDK feature flag, which is off by default. **Fix**: contact your i99dash administrator to enable the SDK on the backend (this is a production-readiness checkbox, not a per-user setting). ### Login hangs at "waiting for authorization…" [#login-hangs-at-waiting-for-authorization] **Symptom**: device-code flow opens the browser, you authorize, but the CLI keeps polling forever. **Root cause** (most common): the browser tab finished but you didn't click the "Approve" button. The backend marks the request `pending` until you explicitly approve. **Fix**: re-open the URL printed by the CLI, click Approve. If lost, kill the CLI (`Ctrl+C`) and re-run `sdk-i99dash login`. ## Publish [#publish] ### `403 Forbidden` on presigned PUT during `sdk-i99dash publish` [#403-forbidden-on-presigned-put-during-sdk-i99dash-publish] **Symptom**: `publish` succeeds at validation + build, then fails on the bundle upload step. **Root cause** (one of): * The bucket's CORS rule doesn't include the origin you're publishing from. The dev-server runs on `http://localhost:5173`; if your local has shifted to a different port, CORS rejects the PUT. * The presigned URL expired (default 5 min from issue). Slow networks on large bundles can hit this. **Fix**: * Restart `sdk-i99dash dev` on the default port (5173). * Re-run `publish` (it requests a fresh presigned URL). ### `publish` succeeds but the catalog never shows the app [#publish-succeeds-but-the-catalog-never-shows-the-app] **Symptom**: terminal reports successful upload + register, but the admin dashboard's mini-apps list doesn't show your app. **Root cause** (likely): the admin dashboard catalog page caches the manifest for a few seconds. Or your account doesn't have catalog read permission. **Fix**: * Hard-refresh the admin page (Cmd/Ctrl + Shift + R). * Confirm `whoami` shows your account is a developer (`sdk-i99dash whoami`). ## Backend [#backend] ### `enable_sdk` is true but `oauth/device/authorize` still 404s [#enable_sdk-is-true-but-oauthdeviceauthorize-still-404s] **Symptom**: you flipped `ENABLE_SDK=true` in DO App Platform, redeployed, but the OAuth endpoints still 404. **Root cause**: the `enable_sdk` setting is gated by a `model_validator` that checks **all four** `MINIAPPS_SPACES_*` env vars are non-empty before honoring the flag. If any one is missing or set to `placeholder`, the validator forces `enable_sdk=false` regardless. **Fix**: confirm all four are set with real values (mint a Spaces key, paste both halves): * `MINIAPPS_SPACES_ENDPOINT` (e.g. `https://fra1.digitaloceanspaces.com`) * `MINIAPPS_SPACES_BUCKET` (e.g. `i99dash-miniapps`) * `MINIAPPS_SPACES_ACCESS_KEY` (SECRET, real value) * `MINIAPPS_SPACES_SECRET_KEY` (SECRET, real value) The backend's `prestart.sh` logs `enable_sdk forced to false: missing X, Y, Z` when this happens — check `doctl apps logs --type=deploy` for that line. ## Privileged mini-apps (`admin-sdk`) [#privileged-mini-apps-admin-sdk] ### `pnpm add @i99dash/admin-sdk` returns 404 / 403 [#pnpm-add-i99dashadmin-sdk-returns-404--403] **Symptom**: ``` ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/@i99dash%2Fadmin-sdk ``` or ``` npm error 403 Forbidden - GET https://npm.pkg.github.com/@i99dash%2fadmin-sdk ``` **Root cause** (one of): 1. You're hitting public npm. `@i99dash/admin-sdk` is on **GitHub Packages**, not npmjs.com — public npm doesn't know about it. 2. You're hitting GitHub Packages but with no token (or a token without `read:packages`). 3. Your GitHub account hasn't been granted access to the package — even with a valid token, restricted packages 403 for unauthorised consumers. **Fix**: ```ini # .npmrc — at the project root @i99dash:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} ``` ```bash export GITHUB_TOKEN=ghp_xxx # classic token with read:packages scope pnpm add @i99dash/admin-sdk ``` If you still 403 after the .npmrc + token fix, your account isn't on the access list — request a `cmdExec.*` permission grant from your i99dash admin. The package is gated, not just scoped. ### `NotInsideHostError` outside the host (Storybook, SSR, Node) [#notinsidehosterror-outside-the-host-storybook-ssr-node] **Symptom**: `AdminClient.fromWindow()` throws `NotInsideHostError: window is undefined` or `host bridge missing on window`. **Root cause**: there is no host bridge — your code is running outside the i99dash host (server-rendered, in a unit-test runner, or in Storybook). **Fix**: guard the construction and render a fallback: ```ts import { AdminClient, NotInsideHostError } from '@i99dash/admin-sdk'; let client: AdminClient | null = null; try { client = AdminClient.fromWindow({ context, catalog }); } catch (err) { if (err instanceof NotInsideHostError) client = null; else throw err; } ``` For tests, use `AdminClient.withBridge({ bridge: new FakeAdminBridge(...) })` instead — see [Privileged mini-apps — Local development](/docs/develop/privileged-apps#path-2--fakeadminbridge-unit-tests-storybook-ssr). ### Every `invoke()` returns `{success: false, error: { code: 'user_consent_missing' }}` [#every-invoke-returns-success-false-error--code-user_consent_missing-] **Symptom**: calls reach the host but every envelope is `user_consent_missing`, regardless of which template. **Root cause** (one of): 1. Your `manifest.json` doesn't list the `cmdExec.*` permission the template requires. The user never saw a consent prompt at install, so the host has no consent record. 2. The user explicitly denied that permission at install. 3. Your dev cert doesn't grant the permission (cert revocation, or never granted). **Fix**: * Confirm `manifest.json` has the right permission: ```json { "permissions": ["cmdExec.read", "cmdExec.control"] } ``` * Re-validate: `sdk-i99dash validate`. * Have the user re-install the app — re-install re-prompts for consent. * Confirm your developer account has `cmdExec.*` granted (see [Privileged mini-apps — Prerequisites](/docs/develop/privileged-apps#prerequisites)). ### `UnknownTemplateError` on a template you know exists server-side [#unknowntemplateerror-on-a-template-you-know-exists-server-side] **Symptom**: `admin.invoke('pm.uninstall', ...)` throws `UnknownTemplateError: unknown template id "pm.uninstall"` even though the migration shipped. **Root cause**: your *client-side* catalog snapshot is stale. The SDK validates `templateId` against `catalog: snapshotFromList(...)` before hitting the bridge, so a stale snapshot blocks valid templates. **Fix**: refresh the catalog the host injects on `window.__i99dashAdminCatalog`. In dev, update your hardcoded `DEV_CATALOG` constant. The host's own SQLite-cached catalog is the authoritative source for the actual dispatch — the client snapshot only drives client-side affordances and the early `UnknownTemplateError` guard. ### `session_cap_expired` envelopes after a long-idle period [#session_cap_expired-envelopes-after-a-long-idle-period] **Symptom**: privileged ops worked yesterday, today every tier-2 call returns `{success: false, error: { code: 'session_cap_expired' }}`. **Root cause**: install-time session cap has a 30-day TTL. After expiry the host triggers a refresh on the next call — but if the refresh itself fails (offline, backend down), the next \~5–30s of calls still see the expired cap. **Fix**: retry once after 500–1000ms — the refresh usually completes in that window. Persistent expiry means the user needs to re-install. ## CI / GitHub Actions [#ci--github-actions] ### `422 Unprocessable Entity` on npm publish with `--provenance` [#422-unprocessable-entity-on-npm-publish-with---provenance] **Symptom**: ``` npm error 422 - Error verifying sigstore provenance bundle: Unsupported GitHub Actions source repository visibility: "private". Only public source repositories are supported when publishing with provenance. ``` **Root cause**: npm's sigstore provenance attestation requires the source GitHub repo to be **public**. Publishing from a private repo with `NPM_CONFIG_PROVENANCE: 'true'` always fails. **Fix** (pick one): * Make the repo public (recommended for SDK distribution — packages are public anyway). * Drop `--provenance` from the workflow. You lose the sigstore-signed link between commit and tarball, but classic-token publish keeps working. ### `gh workflow run` doesn't actually run the publish job [#gh-workflow-run-doesnt-actually-run-the-publish-job] **Symptom**: you trigger the workflow, it completes in seconds with green check, but no packages were published. **Root cause**: the publish job is gated on `releases_created == 'true'`. When release-please runs and finds no qualifying conventional commits, it reports `false`, the publish job is skipped, and the workflow completes successfully without publishing. **Fix**: trigger with `force_publish: true` (manual recovery path): ```bash gh workflow run release-please.yml \ --repo /sdk-i99dash \ -f force_publish=true ``` This bypasses release-please and publishes the versions currently in `package.json`. npm rejects re-publishing existing versions with 403, which is harmless — the loop continues to the next package. --- # AdminBridge (/docs/api/admin-sdk/admin-bridge) `AdminBridge` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # AdminClientContext (/docs/api/admin-sdk/admin-client-context) `AdminClientContext` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/client.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # AdminClientOptions (/docs/api/admin-sdk/admin-client-options) `AdminClientOptions` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/client.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # AdminClient (/docs/api/admin-sdk/admin-client) `AdminClient` — public export from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/client.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # AdminExecRequest (/docs/api/admin-sdk/admin-exec-request) `AdminExecRequest` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # AdminOpResponse (/docs/api/admin-sdk/admin-op-response) `AdminOpResponse` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/types.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/types.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CapabilityResponse (/docs/api/admin-sdk/capability-response) `CapabilityResponse` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/types.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/types.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CatalogSnapshot (/docs/api/admin-sdk/catalog-snapshot) `CatalogSnapshot` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/types.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/types.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CommandTemplate (/docs/api/admin-sdk/command-template) `CommandTemplate` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/types.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/types.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # FakeAdminBridge (/docs/api/admin-sdk/fake-admin-bridge) `FakeAdminBridge` — public export from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # HostAdminBridge (/docs/api/admin-sdk/host-admin-bridge) `HostAdminBridge` — public export from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # @i99dash/admin-sdk (/docs/api/admin-sdk) API reference for the @i99dash/admin-sdk package. {/* @generated by scripts/generate-api-pages.mjs */} Public exports of `@i99dash/admin-sdk`. Each entry links to its auto-generated reference page. * [`AdminClient`](/docs/api/admin-sdk/admin-client) * [`UnknownTemplateError`](/docs/api/admin-sdk/unknown-template-error) * [`AdminClientContext`](/docs/api/admin-sdk/admin-client-context) * [`AdminClientOptions`](/docs/api/admin-sdk/admin-client-options) * [`InvokeOptions`](/docs/api/admin-sdk/invoke-options) * [`FakeAdminBridge`](/docs/api/admin-sdk/fake-admin-bridge) * [`HostAdminBridge`](/docs/api/admin-sdk/host-admin-bridge) * [`AdminBridge`](/docs/api/admin-sdk/admin-bridge) * [`AdminExecRequest`](/docs/api/admin-sdk/admin-exec-request) * [`snapshotFromList`](/docs/api/admin-sdk/snapshot-from-list) * [`AdminOpResponse`](/docs/api/admin-sdk/admin-op-response) * [`CapabilityResponse`](/docs/api/admin-sdk/capability-response) * [`CatalogSnapshot`](/docs/api/admin-sdk/catalog-snapshot) * [`CommandTemplate`](/docs/api/admin-sdk/command-template) * [`ParamRule`](/docs/api/admin-sdk/param-rule) --- # InvokeOptions (/docs/api/admin-sdk/invoke-options) `InvokeOptions` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/client.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ParamRule (/docs/api/admin-sdk/param-rule) `ParamRule` — public type from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/types.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/types.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # snapshotFromList (/docs/api/admin-sdk/snapshot-from-list) `snapshotFromList` — public export from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/types.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/types.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # UnknownTemplateError (/docs/api/admin-sdk/unknown-template-error) `UnknownTemplateError` — public export from `@i99dash/admin-sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/admin-sdk`](/docs/api/admin-sdk) * File: [`packages/admin-sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/admin-sdk/src/client.ts) ## Related [#related] * [`@i99dash/admin-sdk` index](/docs/api/admin-sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # @i99dash/sdk-cli (/docs/api/sdk-cli) API reference for the @i99dash/sdk-cli package. {/* @generated by scripts/generate-api-pages.mjs */} Public exports of `@i99dash/sdk-cli`. Each entry links to its auto-generated reference page. * [`runBuild`](/docs/api/sdk-cli/run-build) * [`makeBetaCommand`](/docs/api/sdk-cli/make-beta-command) * [`runDev`](/docs/api/sdk-cli/run-dev) * [`runDoctor`](/docs/api/sdk-cli/run-doctor) * [`runInit`](/docs/api/sdk-cli/run-init) * [`runLogin`](/docs/api/sdk-cli/run-login) * [`runLogout`](/docs/api/sdk-cli/run-logout) * [`runPublish`](/docs/api/sdk-cli/run-publish) * [`runValidate`](/docs/api/sdk-cli/run-validate) * [`runWhoami`](/docs/api/sdk-cli/run-whoami) --- # makeBetaCommand (/docs/api/sdk-cli/make-beta-command) `makeBetaCommand` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/beta.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/beta.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runBuild (/docs/api/sdk-cli/run-build) `runBuild` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/build.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/build.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runDev (/docs/api/sdk-cli/run-dev) `runDev` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/dev.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/dev.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runDoctor (/docs/api/sdk-cli/run-doctor) `runDoctor` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/doctor.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/doctor.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runInit (/docs/api/sdk-cli/run-init) `runInit` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/init.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/init.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runLogin (/docs/api/sdk-cli/run-login) `runLogin` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/login.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/login.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runLogout (/docs/api/sdk-cli/run-logout) `runLogout` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/logout.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/logout.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runPublish (/docs/api/sdk-cli/run-publish) `runPublish` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/publish.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/publish.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runValidate (/docs/api/sdk-cli/run-validate) `runValidate` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/validate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/validate.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # runWhoami (/docs/api/sdk-cli/run-whoami) `runWhoami` — public export from `@i99dash/sdk-cli`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-cli`](/docs/api/sdk-cli) * File: [`packages/sdk-cli/src/commands/whoami.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-cli/src/commands/whoami.ts) ## Related [#related] * [`@i99dash/sdk-cli` index](/docs/api/sdk-cli) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # BridgeTimeoutError (/docs/api/sdk/bridge-timeout-error) `BridgeTimeoutError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # BridgeTransportError (/docs/api/sdk/bridge-transport-error) `BridgeTransportError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # Bridge (/docs/api/sdk/bridge) `Bridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CallApiFailedError (/docs/api/sdk/call-api-failed-error) `CallApiFailedError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CallOptions (/docs/api/sdk/call-options) `CallOptions` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/client.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CapabilitiesBridge (/docs/api/sdk/capabilities-bridge) `CapabilitiesBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarConnectionListener (/docs/api/sdk/car-connection-listener) `CarConnectionListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/car.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/car.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusBridge (/docs/api/sdk/car-status-bridge) `CarStatusBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusController (/docs/api/sdk/car-status-controller) `CarStatusController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/car.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/car.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusListener (/docs/api/sdk/car-status-listener) `CarStatusListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/car.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/car.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusQuotaExceededError (/docs/api/sdk/car-status-quota-exceeded-error) `CarStatusQuotaExceededError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusUnavailableError (/docs/api/sdk/car-status-unavailable-error) `CarStatusUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MiniAppClient (/docs/api/sdk/client) The host-bridge entry point. One client per mini-app, created at startup. {/* @manual */} Create with `new MiniAppClient()`. See the [installation guide](/docs/getting-started/installation) for setup, and [best practices](/docs/guides/best-practices) for error handling. ## `CallOptions` [#calloptions] Per-call options accepted by `client.callApi()`. --- # ClimateBridge (/docs/api/sdk/climate-bridge) `ClimateBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateController (/docs/api/sdk/climate-controller) `ClimateController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/climate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/climate.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateListener (/docs/api/sdk/climate-listener) `ClimateListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/climate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/climate.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateUnavailableError (/docs/api/sdk/climate-unavailable-error) `ClimateUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ConnectivityBridge (/docs/api/sdk/connectivity-bridge) `ConnectivityBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ConnectivityController (/docs/api/sdk/connectivity-controller) `ConnectivityController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/connectivity.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/connectivity.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ConnectivityListener (/docs/api/sdk/connectivity-listener) `ConnectivityListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/connectivity.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/connectivity.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ConnectivityUnavailableError (/docs/api/sdk/connectivity-unavailable-error) `ConnectivityUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # createClientOrSSR (/docs/api/sdk/create-client-or-ssr) `createClientOrSSR` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/ssr.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/ssr.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # Errors (/docs/api/sdk/errors) Error codes thrown by the SDK. Switch on `code`, not class names. {/* @manual */} All SDK errors extend `SDKError` and carry a stable `code` field. [Best practices](/docs/guides/best-practices#errors) explains the rules. --- # HostBridgeApi (/docs/api/sdk/host-bridge-api) `HostBridgeApi` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # HostBridge (/docs/api/sdk/host-bridge) `HostBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # HOST_EVENTS_GLOBAL (/docs/api/sdk/host-events-global) `HOST_EVENTS_GLOBAL` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # HOST_GLOBAL (/docs/api/sdk/host-global) `HOST_GLOBAL` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # @i99dash/sdk (/docs/api/sdk) Runtime client for mini-apps — host bridge, callApi, getContext, car status. {/* @manual */} Entry point: `import { MiniAppClient } from '@i99dash/sdk'`. * [`MiniAppClient` and `CallOptions`](/docs/api/sdk/client) * [Errors](/docs/api/sdk/errors) --- # InvalidResponseError (/docs/api/sdk/invalid-response-error) `InvalidResponseError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isCapabilitiesBridge (/docs/api/sdk/is-capabilities-bridge) `isCapabilitiesBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isCarStatusBridge (/docs/api/sdk/is-car-status-bridge) `isCarStatusBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isClimateBridge (/docs/api/sdk/is-climate-bridge) `isClimateBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isConnectivityBridge (/docs/api/sdk/is-connectivity-bridge) `isConnectivityBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isMediaBridge (/docs/api/sdk/is-media-bridge) `isMediaBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isSystemBridge (/docs/api/sdk/is-system-bridge) `isSystemBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isVehicleDiagnosticsBridge (/docs/api/sdk/is-vehicle-diagnostics-bridge) `isVehicleDiagnosticsBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # isVehicleEnvironmentBridge (/docs/api/sdk/is-vehicle-environment-bridge) `isVehicleEnvironmentBridge` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # LEGACY_HOST_GLOBAL (/docs/api/sdk/legacy-host-global) `LEGACY_HOST_GLOBAL` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaBridge (/docs/api/sdk/media-bridge) `MediaBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaController (/docs/api/sdk/media-controller) `MediaController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/media.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaListener (/docs/api/sdk/media-listener) `MediaListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/media.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaUnavailableError (/docs/api/sdk/media-unavailable-error) `MediaUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MiniAppClient (/docs/api/sdk/mini-app-client) `MiniAppClient` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/client.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/client.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # NotInsideHostError (/docs/api/sdk/not-inside-host-error) `NotInsideHostError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # PermissionDeniedAggregator (/docs/api/sdk/permission-denied-aggregator) `PermissionDeniedAggregator` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/permission-denied.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/permission-denied.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # PermissionDeniedListener (/docs/api/sdk/permission-denied-listener) `PermissionDeniedListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/permission-denied.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/permission-denied.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # resolveHostApi (/docs/api/sdk/resolve-host-api) `resolveHostApi` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SDKErrorCode (/docs/api/sdk/sdk-error-code) `SDKErrorCode` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SDKError (/docs/api/sdk/sdk-error) `SDKError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SystemBridge (/docs/api/sdk/system-bridge) `SystemBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SystemController (/docs/api/sdk/system-controller) `SystemController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/system.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SystemListener (/docs/api/sdk/system-listener) `SystemListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/system.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SystemUnavailableError (/docs/api/sdk/system-unavailable-error) `SystemUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleDiagnosticsBridge (/docs/api/sdk/vehicle-diagnostics-bridge) `VehicleDiagnosticsBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleDiagnosticsController (/docs/api/sdk/vehicle-diagnostics-controller) `VehicleDiagnosticsController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleDiagnosticsListener (/docs/api/sdk/vehicle-diagnostics-listener) `VehicleDiagnosticsListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleDiagnosticsUnavailableError (/docs/api/sdk/vehicle-diagnostics-unavailable-error) `VehicleDiagnosticsUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleEnvironmentBridge (/docs/api/sdk/vehicle-environment-bridge) `VehicleEnvironmentBridge` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleEnvironmentController (/docs/api/sdk/vehicle-environment-controller) `VehicleEnvironmentController` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/vehicle-environment.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/vehicle-environment.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleEnvironmentListener (/docs/api/sdk/vehicle-environment-listener) `VehicleEnvironmentListener` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/vehicle-environment.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/vehicle-environment.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleEnvironmentUnavailableError (/docs/api/sdk/vehicle-environment-unavailable-error) `VehicleEnvironmentUnavailableError` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/errors.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/errors.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # WindowWithHost (/docs/api/sdk/window-with-host) `WindowWithHost` — public type from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/bridge.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/bridge.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # withTimeout (/docs/api/sdk/with-timeout) `withTimeout` — public export from `@i99dash/sdk`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk`](/docs/api/sdk) * File: [`packages/sdk/src/util/timeout.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk/src/util/timeout.ts) ## Related [#related] * [`@i99dash/sdk` index](/docs/api/sdk) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # buildServer (/docs/api/sdk-dev-server/build-server) `buildServer` — public export from `@i99dash/sdk-dev-server`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-dev-server`](/docs/api/sdk-dev-server) * File: [`packages/sdk-dev-server/src/server.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-dev-server/src/server.ts) ## Related [#related] * [`@i99dash/sdk-dev-server` index](/docs/api/sdk-dev-server) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # DevServerStatePatch (/docs/api/sdk-dev-server/dev-server-state-patch) `DevServerStatePatch` — public type from `@i99dash/sdk-dev-server`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-dev-server`](/docs/api/sdk-dev-server) * File: [`packages/sdk-dev-server/src/state/state-store.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-dev-server/src/state/state-store.ts) ## Related [#related] * [`@i99dash/sdk-dev-server` index](/docs/api/sdk-dev-server) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # DevServerState (/docs/api/sdk-dev-server/dev-server-state) `DevServerState` — public type from `@i99dash/sdk-dev-server`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-dev-server`](/docs/api/sdk-dev-server) * File: [`packages/sdk-dev-server/src/state/state-store.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-dev-server/src/state/state-store.ts) ## Related [#related] * [`@i99dash/sdk-dev-server` index](/docs/api/sdk-dev-server) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # FixtureStore (/docs/api/sdk-dev-server/fixture-store) `FixtureStore` — public export from `@i99dash/sdk-dev-server`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-dev-server`](/docs/api/sdk-dev-server) * File: [`packages/sdk-dev-server/src/state/fixture-store.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-dev-server/src/state/fixture-store.ts) ## Related [#related] * [`@i99dash/sdk-dev-server` index](/docs/api/sdk-dev-server) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # Fixture (/docs/api/sdk-dev-server/fixture) `Fixture` — public type from `@i99dash/sdk-dev-server`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-dev-server`](/docs/api/sdk-dev-server) * File: [`packages/sdk-dev-server/src/state/fixture-store.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-dev-server/src/state/fixture-store.ts) ## Related [#related] * [`@i99dash/sdk-dev-server` index](/docs/api/sdk-dev-server) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # @i99dash/sdk-dev-server (/docs/api/sdk-dev-server) API reference for the @i99dash/sdk-dev-server package. {/* @generated by scripts/generate-api-pages.mjs */} Public exports of `@i99dash/sdk-dev-server`. Each entry links to its auto-generated reference page. * [`StateStore`](/docs/api/sdk-dev-server/state-store) * [`DevServerState`](/docs/api/sdk-dev-server/dev-server-state) * [`DevServerStatePatch`](/docs/api/sdk-dev-server/dev-server-state-patch) * [`FixtureStore`](/docs/api/sdk-dev-server/fixture-store) * [`Fixture`](/docs/api/sdk-dev-server/fixture) * [`buildServer`](/docs/api/sdk-dev-server/build-server) --- # StateStore (/docs/api/sdk-dev-server/state-store) `StateStore` — public export from `@i99dash/sdk-dev-server`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/sdk-dev-server`](/docs/api/sdk-dev-server) * File: [`packages/sdk-dev-server/src/state/state-store.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/sdk-dev-server/src/state/state-store.ts) ## Related [#related] * [`@i99dash/sdk-dev-server` index](/docs/api/sdk-dev-server) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ApiMethodSchema (/docs/api/types/api-method-schema) `ApiMethodSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/call-api.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/call-api.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ApiMethod (/docs/api/types/api-method) `ApiMethod` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/call-api.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/call-api.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CallApiRequestSchema (/docs/api/types/call-api-request-schema) `CallApiRequestSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/call-api.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/call-api.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CallApiRequest (/docs/api/types/call-api-request) `CallApiRequest` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/call-api.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/call-api.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CallApiResponseSchema (/docs/api/types/call-api-response-schema) `CallApiResponseSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/call-api.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/call-api.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CallApiResponse (/docs/api/types/call-api-response) `CallApiResponse` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/call-api.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/call-api.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarConnectionStateSchema (/docs/api/types/car-connection-state-schema) `CarConnectionStateSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarConnectionState (/docs/api/types/car-connection-state) `CarConnectionState` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarDoorStateSchema (/docs/api/types/car-door-state-schema) `CarDoorStateSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarDoorState (/docs/api/types/car-door-state) `CarDoorState` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarDoorsSchema (/docs/api/types/car-doors-schema) `CarDoorsSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarDoors (/docs/api/types/car-doors) `CarDoors` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusSchema (/docs/api/types/car-status-schema) `CarStatusSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusStalenessSchema (/docs/api/types/car-status-staleness-schema) `CarStatusStalenessSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatusStaleness (/docs/api/types/car-status-staleness) `CarStatusStaleness` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # CarStatus (/docs/api/types/car-status) `CarStatus` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/car-status.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/car-status.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateModeSchema (/docs/api/types/climate-mode-schema) `ClimateModeSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/climate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/climate.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateMode (/docs/api/types/climate-mode) `ClimateMode` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/climate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/climate.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateSnapshotSchema (/docs/api/types/climate-snapshot-schema) `ClimateSnapshotSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/climate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/climate.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ClimateSnapshot (/docs/api/types/climate-snapshot) `ClimateSnapshot` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/climate.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/climate.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ConnectivitySnapshotSchema (/docs/api/types/connectivity-snapshot-schema) `ConnectivitySnapshotSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/connectivity.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/connectivity.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # ConnectivitySnapshot (/docs/api/types/connectivity-snapshot) `ConnectivitySnapshot` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/connectivity.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/connectivity.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # DistanceUnitSchema (/docs/api/types/distance-unit-schema) `DistanceUnitSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # DistanceUnit (/docs/api/types/distance-unit) `DistanceUnit` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # GearPositionSchema (/docs/api/types/gear-position-schema) `GearPositionSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # GearPosition (/docs/api/types/gear-position) `GearPosition` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # HostCapabilitiesSchema (/docs/api/types/host-capabilities-schema) `HostCapabilitiesSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/capabilities.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/capabilities.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # HostCapabilities (/docs/api/types/host-capabilities) `HostCapabilities` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/capabilities.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/capabilities.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # @i99dash/types (/docs/api/types) API reference for the @i99dash/types package. {/* @generated by scripts/generate-api-pages.mjs */} Public exports of `@i99dash/types`. Each entry links to its auto-generated reference page. * [`MiniAppContextSchema`](/docs/api/types/mini-app-context-schema) * [`MiniAppContext`](/docs/api/types/mini-app-context) * [`ApiMethodSchema`](/docs/api/types/api-method-schema) * [`ApiMethod`](/docs/api/types/api-method) * [`CallApiRequestSchema`](/docs/api/types/call-api-request-schema) * [`CallApiRequest`](/docs/api/types/call-api-request) * [`CallApiResponseSchema`](/docs/api/types/call-api-response-schema) * [`CallApiResponse`](/docs/api/types/call-api-response) * [`LocaleMapSchema`](/docs/api/types/locale-map-schema) * [`LocaleMap`](/docs/api/types/locale-map) * [`MiniAppManifestSchema`](/docs/api/types/mini-app-manifest-schema) * [`MiniAppManifest`](/docs/api/types/mini-app-manifest) * [`CarStatusSchema`](/docs/api/types/car-status-schema) * [`CarStatus`](/docs/api/types/car-status) * [`CarStatusStalenessSchema`](/docs/api/types/car-status-staleness-schema) * [`CarStatusStaleness`](/docs/api/types/car-status-staleness) * [`CarDoorsSchema`](/docs/api/types/car-doors-schema) * [`CarDoors`](/docs/api/types/car-doors) * [`CarDoorStateSchema`](/docs/api/types/car-door-state-schema) * [`CarDoorState`](/docs/api/types/car-door-state) * [`CarConnectionStateSchema`](/docs/api/types/car-connection-state-schema) * [`CarConnectionState`](/docs/api/types/car-connection-state) * [`HostCapabilitiesSchema`](/docs/api/types/host-capabilities-schema) * [`HostCapabilities`](/docs/api/types/host-capabilities) * [`MediaSnapshotSchema`](/docs/api/types/media-snapshot-schema) * [`MediaSnapshot`](/docs/api/types/media-snapshot) * [`MediaSourceSchema`](/docs/api/types/media-source-schema) * [`MediaSource`](/docs/api/types/media-source) * [`MediaPlayStateSchema`](/docs/api/types/media-play-state-schema) * [`MediaPlayState`](/docs/api/types/media-play-state) * [`ClimateSnapshotSchema`](/docs/api/types/climate-snapshot-schema) * [`ClimateSnapshot`](/docs/api/types/climate-snapshot) * [`ClimateModeSchema`](/docs/api/types/climate-mode-schema) * [`ClimateMode`](/docs/api/types/climate-mode) * [`VehicleDiagnosticsSnapshotSchema`](/docs/api/types/vehicle-diagnostics-snapshot-schema) * [`VehicleDiagnosticsSnapshot`](/docs/api/types/vehicle-diagnostics-snapshot) * [`GearPositionSchema`](/docs/api/types/gear-position-schema) * [`GearPosition`](/docs/api/types/gear-position) * [`TirePressureSchema`](/docs/api/types/tire-pressure-schema) * [`TirePressure`](/docs/api/types/tire-pressure) * [`VehicleEnvironmentSnapshotSchema`](/docs/api/types/vehicle-environment-snapshot-schema) * [`VehicleEnvironmentSnapshot`](/docs/api/types/vehicle-environment-snapshot) * [`SystemSnapshotSchema`](/docs/api/types/system-snapshot-schema) * [`SystemSnapshot`](/docs/api/types/system-snapshot) * [`DistanceUnitSchema`](/docs/api/types/distance-unit-schema) * [`DistanceUnit`](/docs/api/types/distance-unit) * [`TemperatureUnitSchema`](/docs/api/types/temperature-unit-schema) * [`TemperatureUnit`](/docs/api/types/temperature-unit) * [`OtaStatusSchema`](/docs/api/types/ota-status-schema) * [`OtaStatus`](/docs/api/types/ota-status) * [`ConnectivitySnapshotSchema`](/docs/api/types/connectivity-snapshot-schema) * [`ConnectivitySnapshot`](/docs/api/types/connectivity-snapshot) * [`NetworkTypeSchema`](/docs/api/types/network-type-schema) * [`NetworkType`](/docs/api/types/network-type) --- # LocaleMapSchema (/docs/api/types/locale-map-schema) `LocaleMapSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/manifest.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/manifest.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # LocaleMap (/docs/api/types/locale-map) `LocaleMap` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/manifest.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/manifest.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaPlayStateSchema (/docs/api/types/media-play-state-schema) `MediaPlayStateSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/media.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaPlayState (/docs/api/types/media-play-state) `MediaPlayState` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/media.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaSnapshotSchema (/docs/api/types/media-snapshot-schema) `MediaSnapshotSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/media.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaSnapshot (/docs/api/types/media-snapshot) `MediaSnapshot` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/media.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaSourceSchema (/docs/api/types/media-source-schema) `MediaSourceSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/media.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MediaSource (/docs/api/types/media-source) `MediaSource` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/media.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/media.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MiniAppContextSchema (/docs/api/types/mini-app-context-schema) `MiniAppContextSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/context.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/context.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MiniAppContext (/docs/api/types/mini-app-context) `MiniAppContext` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/context.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/context.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MiniAppManifestSchema (/docs/api/types/mini-app-manifest-schema) `MiniAppManifestSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/manifest.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/manifest.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # MiniAppManifest (/docs/api/types/mini-app-manifest) `MiniAppManifest` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/manifest.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/manifest.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # NetworkTypeSchema (/docs/api/types/network-type-schema) `NetworkTypeSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/connectivity.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/connectivity.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # NetworkType (/docs/api/types/network-type) `NetworkType` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/connectivity.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/connectivity.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # OtaStatusSchema (/docs/api/types/ota-status-schema) `OtaStatusSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # OtaStatus (/docs/api/types/ota-status) `OtaStatus` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SystemSnapshotSchema (/docs/api/types/system-snapshot-schema) `SystemSnapshotSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # SystemSnapshot (/docs/api/types/system-snapshot) `SystemSnapshot` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # TemperatureUnitSchema (/docs/api/types/temperature-unit-schema) `TemperatureUnitSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # TemperatureUnit (/docs/api/types/temperature-unit) `TemperatureUnit` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/system.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/system.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # TirePressureSchema (/docs/api/types/tire-pressure-schema) `TirePressureSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # TirePressure (/docs/api/types/tire-pressure) `TirePressure` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleDiagnosticsSnapshotSchema (/docs/api/types/vehicle-diagnostics-snapshot-schema) `VehicleDiagnosticsSnapshotSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleDiagnosticsSnapshot (/docs/api/types/vehicle-diagnostics-snapshot) `VehicleDiagnosticsSnapshot` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-diagnostics.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-diagnostics.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleEnvironmentSnapshotSchema (/docs/api/types/vehicle-environment-snapshot-schema) `VehicleEnvironmentSnapshotSchema` — public export from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-environment.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-environment.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # VehicleEnvironmentSnapshot (/docs/api/types/vehicle-environment-snapshot) `VehicleEnvironmentSnapshot` — public type from `@i99dash/types`. {/* @generated by scripts/generate-api-pages.mjs */} ## Source [#source] * Package: [`@i99dash/types`](/docs/api/types) * File: [`packages/types/src/vehicle-environment.ts`](https://github.com/i99dash/sdk-i99dash/blob/main/packages/types/src/vehicle-environment.ts) ## Related [#related] * [`@i99dash/types` index](/docs/api/types) — every export of this package * [Concepts](/docs/concepts) — the bridge, calling your backend, error model * [Guides](/docs/guides) — task-oriented walkthroughs --- # Pick a framework (/docs/getting-started/frameworks) Mini-apps are static bundles, so any framework that emits a static export works. Here's the one-line summary for the common ones. The host loads your bundle as a sandboxed web view, so **any framework that produces static HTML/JS/CSS works**. The framework only changes two things: what you put in `sdk.config.json` (the build command + output dir), and where you mount the SDK calls (in a client component that runs after hydration, never during SSR). ## Frameworks not on this list [#frameworks-not-on-this-list] Anything that supports static export will work — Vite, SvelteKit (adapter-static), Astro, Eleventy, Hugo, Jekyll, plain Webpack/Rollup configs. The pattern is always the same: 1. Configure your framework for static output. 2. In `sdk.config.json`, set `appRoot` to its output dir and `buildCommand` to its build command. 3. Anything calling `MiniAppClient.fromWindow()` runs **after** hydration, never during SSR. If you wire one up, [open a docs PR](https://github.com/i99dash/doc-sdk-i99dash/issues/new) and we'll add it here. --- # Next.js (/docs/getting-started/frameworks/nextjs) App router + static export. The two pitfalls to know up front, plus a working pattern. Use Next.js when you want file-based routing, SSG, React server components, or any ecosystem integration. **Two pitfalls to know up front**: 1. The host bridge lives on `window`. It **does not exist during SSR**. Any code that reads it must run in a Client Component inside `useEffect` / `componentDidMount`. 2. For publishing, you want a **fully static** output (`output: 'export'`). The host doesn't run a Next.js server — your mini-app is shipped as bytes to the CDN. ## Install [#install] ```bash pnpm add @i99dash/sdk pnpm add -D @i99dash/sdk-cli ``` ## Static export [#static-export] `next.config.mjs`: ```js export default { output: 'export', trailingSlash: true, images: { unoptimized: true }, }; ``` ## `sdk.config.json` [#sdkconfigjson] ```json { "appRoot": "./out", "mocksDir": "./mocks", "buildCommand": "next build" } ``` `next build` with `output: 'export'` drops the static site in `./out`; the SDK copies that to `dist/` and tarballs it. ## Client-component pattern [#client-component-pattern] `app/fuel/Fuel.client.tsx`: ```tsx 'use client'; import { useEffect, useState } from 'react'; import { MiniAppClient, type MiniAppContext } from '@i99dash/sdk'; export default function Fuel() { const [ctx, setCtx] = useState(null); useEffect(() => { const client = MiniAppClient.fromWindow(); client .getContext() .then(setCtx) .catch((e) => console.error(e)); }, []); if (!ctx) return

loading…

; return
{JSON.stringify(ctx, null, 2)}
; } ``` `app/fuel/page.tsx`: ```tsx import Fuel from './Fuel.client'; export default function Page() { return ; } ``` ## Running both dev servers [#running-both-dev-servers] ```bash # terminal 1 — Next.js pnpm next dev --port 3000 # terminal 2 — sdk-dev-server pointing at a static build pnpm next build && pnpm next export sdk-i99dash dev --port 5173 ``` Or inline the shim during dev: add ` ``` ## Running both dev servers [#running-both-dev-servers] ```bash pnpm nuxt dev --port 3000 # terminal 1 pnpm nuxt generate sdk-i99dash dev --port 5173 # terminal 2 ``` Same trick as Next.js: during dev you can inline ` ``` `sdk-i99dash dev` static-serves `appRoot/` unchanged — no build step. `sdk-i99dash build` copies `appRoot/` → `distDir/` (plus `manifest.json`). `sdk-i99dash publish` tarballs and uploads. ## When this is the right choice [#when-this-is-the-right-choice] Recommended when your app fits in a single HTML page plus a couple of JS / CSS files. No bundler means the **fastest possible cold start** on the head unit — which matters: the head-unit CPU is roughly an entry-level phone from 2018. For anything bigger than a single screen with a couple of buttons, reach for [Next.js](/docs/getting-started/frameworks/nextjs) or [Nuxt](/docs/getting-started/frameworks/nuxt). ## Related [#related] * [Quickstart](/docs/getting-started) — the 5-minute path uses the vanilla template by default. * [Local development](/docs/develop/local-dev) — fixture grammar.