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
- 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
| 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
Path 1 — publish directly to beta
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
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
Invite a tester
sdk-i99dash beta invite com.example.myapp @alice
sdk-i99dash beta invite com.example.myapp alice # @ prefix is optionalThe CLI strips a leading @ so you don't have to think about it.
Invite multiple testers in one shot
# 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
sdk-i99dash beta testers com.example.myappOutput:
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
sdk-i99dash beta revoke com.example.myapp <user_id>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
Two ways:
# Copy the current beta bundle into the production track
sdk-i99dash beta promote-production com.example.myappThis 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.
# Or pull the beta without promoting (e.g. you found a critical bug)
sdk-i99dash beta demote com.example.myappThis clears the beta pointer. Testers fall back to production on next launch.
End-to-end example
Here's the full lifecycle of a single beta cycle:
# 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.myappSecurity: 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:
- Wait for the user to open i99dash.
- Run
beta testers <app_id>and look for their row. - If their status is still
invitedafter 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
From the user's side:
- Opens i99dash on their car or phone.
- Sees a Beta available card on the mini-app the dev invited them to.
- Taps the card → consent sheet (with your
--release-notestext). - 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
| 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
- Publishing — full publish flow,
including how
--trackinteracts with the existing pipeline. - Authentication — beta commands need the same API key.
runPublishreference — programmatic entry point for CI.