Upload your app
End-to-end publish flow for an app you already wrote — from CLI install to the catalog row going live.
You've built (or are building) a mini-app and want to get it live on the i99dash catalog. This page walks through the whole thing: what you need, what to run, what happens after you hit publish, and what to do when something fails.
If you're starting from scratch and want scaffolding, read the Quickstart instead — it gets you from nothing to a published hello-world in five minutes. This page is for devs who already have their own app code.
Before you start
You'll need:
- Your app code. Any static site works: plain HTML, a Next.js
project with
output: 'export', a Vite / Vue / Svelte build, etc. The host loads your bundle into a sandboxed web view, so anything that compiles down to static files is fair game. - Node ≥ 20 and a package manager (pnpm, npm, or yarn). New machine? See Installation for the full prerequisites.
- An i99dash user account. Sign up first if you don't have one; you'll approve the CLI's login request against this account in step 2.
Step 1 — install the CLI
No global install needed:
pnpm dlx @i99dash/sdk-cli --helpIf you'd rather have sdk-i99dash on $PATH:
pnpm add -g @i99dash/sdk-cliStep 2 — log in
sdk-i99dash loginThe CLI prints a user code and opens the admin dashboard's device authorisation page in your browser. You sign in with your i99dash account and approve the code. The CLI polls in the background; 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. Full flow — including headless / CI machines and key rotation — in Authentication.
Verify:
sdk-i99dash whoamiShould print your dev id and email.
Step 3 — describe your app (manifest.json)
The manifest is the catalog row for your app. It lives at the root of your project and has to be there before you can publish. Minimal example:
{
"id": "fuel_prices",
"name": { "en": "Fuel Prices", "ar": "أسعار الوقود" },
"iconUrl": "https://your-cdn.example.com/icons/fuel.png",
"url": "https://your-cdn.example.com/fuel-prices/",
"version": "1.0.0",
"category": "info"
}Three rules the CLI and backend are strict about. Getting these wrong once is fine; getting them wrong after publish is painful.
idis forever. URL-safe, globally unique, baked into every pinned home-screen shortcut on users' devices. Rotating it orphans every launcher icon anyone ever pinned. Pick something you won't regret.versionmust increment on every publish. The backend rejects a resubmission with the same(id, version). Semver (1.2.3) is the convention; any monotonically increasing string works.urlandiconUrlmust be on the host allow-list. Your chosen origin needs to be in the host's allow-list config — coordinate with ops before publishing.
Full field reference (locales, minHostVersion, safeWhileDriving,
zod schema): MiniAppManifest.
Step 4 — tell the CLI where your app lives (sdk.config.json)
The CLI needs two things: the directory that holds your built
output (appRoot), and how to produce it (buildCommand, optional).
Also at the project root:
{
"appRoot": "./dist",
"buildCommand": "pnpm build"
}The common shapes:
| Project type | appRoot | buildCommand |
|---|---|---|
| Plain HTML / static | ./src | (omit) |
| Next.js (static export) | ./out | next build |
| Vite / Vue / Svelte / Rollup | ./dist | vite build |
| Anything else | your dir | your build cmd |
The CLI runs buildCommand (if set), tarballs appRoot, and
uploads the tarball. It does not bundle, transpile, or minify
for you — that's your build's job.
Step 5 — dry-run first
Before your first live upload, run:
sdk-i99dash publish --dry-runThis validates the manifest, runs your build, and produces the
tarball in your temp directory. It prints the bundle size + SHA-256,
then stops — no upload, no submission. Use it to catch the common
mistakes (missing locale on name, http:// instead of https://,
origin not allow-listed) before the backend records them.
Step 6 — ship it
sdk-i99dash publishWhat the CLI does next:
POST /api/v1/mini-apps/upload-url— asks the backend for a presigned upload URL and reserves a bundle id.PUT <presigned-url>— streams your tarball directly to the CDN.POST /api/v1/mini-apps/submit— registers the manifest + bundle id with the catalog.
What happens after publish
The CLI prints two URLs on success:
- A status URL where you can track review state.
- A CDN URL where your bundle is now fetchable (host-managed origin; the exact base URL is set by ops).
Your submission lands in one of two states:
auto-approved— your app is live in the catalog immediately. This is the default for apps you own.pending— a human review is queued. You can keep iterating locally; re-runningpublishwith a bumpedversionstacks a new bundle under the same review ticket.
Check back later with:
sdk-i99dash whoami # your accountOr reopen the status URL for a specific submission.
Iterating
Every update is just another publish:
# bump version in manifest.json
sdk-i99dash publishSame (id, version) is rejected. Same id with a new version is
the update path — that's it, no separate "update" command. The host
picks up the new bundle on the user's next launch.
For how to choose patch / minor / major, see Publishing — versioning strategy.
Common failures
| Exit code | Usually means | Fix |
|---|---|---|
2 not logged in | API key missing or revoked. | Re-run sdk-i99dash login. |
3 manifest bad | Zod validation failed. | Read the zod error. Most common: missing name locale, http:// URL. |
4 network | Transport failure mid-upload. | Retry. If the presigned URL expired, the CLI re-requests one. |
5 backend reject | Backend refused the submission. | Almost always VERSION_CONFLICT — bump version. Also: quota, origin not allow-listed. |
6 local IO | Disk or temp-dir error building the tarball. | Check free space in your system temp directory. |
CI recipe
GitHub Actions, runs on tag push:
name: Publish mini-app
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: sdk-i99dash validate
- run: sdk-i99dash build
- run: sdk-i99dash publish
env:
I99DASH_API_KEY: ${{ secrets.I99DASH_API_KEY }}Use validate as a PR check; reserve publish for tag-triggered
workflows. For how to provision the I99DASH_API_KEY secret on a
headless machine, see
Authentication — CI / headless machines.
Where to go next
- Local development — fixture grammar for realistic mocked responses + driving-state simulation.
MiniAppClient— full reference for the runtime bridge.