# Wire AI > Wire AI is the AI Growth Engineer for Mobile Apps. Analytics tells you where users left; Wire tests four ways to keep them. It generates four variants for each tested stage of your users' journey, runs them live on real users, reads which one keeps them, and keeps the winner. You set the goal; the AI runs the loop. ## What Wire AI is Most users open a new app once and never come back. Wire is one system that runs the whole in-app journey and keeps what works. Every moment of that journey runs as pre-generated variants, and each user is assigned one of them: the onboarding, the value for their stated need, a feature they never found, a review or feedback ask when the timing is right. Nothing is written live for a user. All four tested acts are decided by the same AI, so the moments connect instead of sitting in four separate tools. The credibility boundary: you define what success means, Wire decides how to reach it. It never touches your pricing, roadmap, or marketing. Across 1,000+ apps, average Day-1 retention sits near 24% (AppsFlyer). Your first flow goes live in an afternoon. ## The five journey stages (ruled 2026-08-14; four are variant-tested, one is the engine step) 1. Onboarding. Four pre-generated versions, varying the order of the questions and how many there are. 2. Added value. Four versions, varying what appears first. 3. AI personalization. NO variants and no winner. The engine step: it reads the onboarding answers plus what the app already has, and sets this user's initial app state. 4. In-app review. Four versions, varying which moment counts as the win. 5. In-app questionnaire. Four versions, varying when to ask and what to ask. Nothing is written live for a user. The variants are pre-generated and served deterministically, assignment is sticky, and the winner is locked as a static config. "Walkthrough" is NOT one of the stages. Roadmap (not shipped): announcements. You ship the fix, Wire tells the user who asked. Edit the goal, questions, and model in the console we provision for you, no app release. Account signup provisions a console for keys and analytics, but that goal / questions / model editor inside it is roadmap and not shipped. The instant-key door provisions no console at all, so never tell someone who used that door to open a dashboard. ## Proof A live consumer app Malik operates runs twenty variants across the whole user journey on its own, discovered who its users really were, and surfaced the messaging that moved installs from 100 a day to 1,000. It took activation from 69% to 93%, now frozen as the control. The loop found the words that sell the app. Supporting: 250k monthly users on a consumer app he led; 9M monthly users on an app he shipped; 20+ apps shipped. Wire runs on Malik's own apps first. ## Pricing - The founding cohort: free. 15 founder seats get the whole engine (all four surfaces, the insights brief, analytics, and a 30-minute install call with Malik). Not a discount, an exchange: a named case study, permission to use your activation metrics, and real feedback. 4 of 15 seats are filled, then the door closes. - Analytics: free to 299k events a month, forever, on any app. One command to install. See where users drop first, then turn on the engine when you're ready. - After the cohort, three plans, priced on how much of the users' journey you test at once (a variant is one pre-generated version of a journey stage, and the quota is how many run live at once). Free: €0 forever, 5 variants live on onboarding, one learning report you file every two weeks, analytics to 299k events a month, 1 app. Starter: €49 a month, 10 variants across two surfaces, one report you file a week, 1M events a month, 1 app. Pro: €149 a month, 20 variants across the whole journey, reports you file on demand, 3M events a month, 3 apps. Early adopters get 2 months free, then 40% off Starter and Pro until the end of the year. Every plan carries the insights brief and analytics, and no plan meters experiments. Ratified by Malik 2026-08-06, and it is DISPLAY ONLY today: billing is not switched on, so nothing charges yet. - The value anchor: the alternative is a growth hire at around €6,000 a month, or you doing it by hand at the cost of everything you're not building. ## Getting a key (the self-serve door) - https://getwireai.com/signup is public and free. No card, no call, no approval queue. Two doors sit behind it and the page renders whichever one is open. - Account signup, the door when it is open: an app name and an email in, a confirmation link out. Nothing exists until that link is opened. Choose a password there and Wire creates the app, the API key and your console in one step. The key is bound to the account, so GET /v1/me/apps and the console both return it whenever you ask, and losing it costs you nothing. - The instant-key door, what the page falls back to while account signup is switched off: an app name in, a working API key, the app id and the server URL back on the page, plus an integration snippet that already carries the key. It creates an app and a key, never a client and never a password, so the key is returned once and nothing can give it to you a second time. Copy it when you see it. Losing it means signing up again for a new app. - Which door a person used decides what to tell them. Never tell someone who took the instant key to log in, and never tell someone with an account that their key is gone for good. - What a new app runs, from either door: the scripted starter flow, two questions, no model calls. Enough to see the SDK render a real flow on a device and watch the events land. Not Wire writing variants and testing them on your users, and an account does not change that. Turning the AI flow on is a manual step today: send the app id to malik@getwireai.com. - The app lands on the Free plan. The key is a tenant key, safe in the app bundle, and it works against the production server. ## Key facts (fact-checked, cleared for publication) - First adaptive card in ~130ms (flow cache). - EU data residency on eu-central-1. Serverless, no shared database. - No model lock-in: Claude by default, switch to OpenAI or GPT-4o-mini with one setting. - Renders native React Native components in your own binary. No WebView, no remote code. Passes app review (Apple guideline 2.5.2 safe, server-driven UI from JSON). - You set the goal and hold the surfaces. You can freeze any winner; the pilot app froze its 93% activation flow as the control. - Built by Malik Chohra: 9 years shipping software, 7 in React Native. 250k monthly users on a consumer app, rating taken from 4.3 to 4.9; 9M users at 96% crash-free on another. ## Open source Wire AI ships its generative-UI layer as the open-source Wire RN SDK for React Native. The AI decides the UI at runtime and Wire RN renders real native components from the model output. MIT licensed. A2A protocol today, A2UI arriving in v0.3. Streaming by default. First card in ~130ms from the flow cache. 136 tests. Adapters: OpenAI, Ollama, LMStudio, Webhook, A2A. - Open source page: https://getwireai.com/generative-ui-react-native - npm: https://www.npmjs.com/package/wireai-rn - GitHub: https://github.com/chohra-med/wireai-rn ## For AI agents (integrate Wire AI into an app) - [SDK agent brief (AGENTS.md)](https://github.com/chohra-med/wireai-rn/blob/main/AGENTS.md): goal, API surface, integration steps, gotchas, read this first. - MCP server (`wireai-mcp`): hosted remote endpoint at https://wireai-mcp.fly.dev/mcp, connect your agent with `claude mcp add --transport http wireai https://wireai-mcp.fly.dev/mcp --header "Authorization: Bearer "` (per-request bearer; a tenant `wai_` key reaches the read tools, the admin key the management tools + A/B/C/D experiments). No install, no login step; a stdio-from-checkout fallback also exists. Docs: https://getwireai.com/docs/mcp - [Copy-paste integration prompt](https://github.com/chohra-med/wireai-rn/blob/main/docs/agent-integration-prompt.md): hand to your coding agent (Cursor / Claude Code / Copilot / Codex) to integrate end-to-end. - [Integrate via backend (A2A)](https://github.com/chohra-med/wireai-rn/blob/main/docs/integrate-via-backend.md) · [Expo setup](https://github.com/chohra-med/wireai-rn/blob/main/docs/expo-integration.md). - Drop-in onboarding kit ``, themed; props + ordered steps in its AGENTS.md. ## Integrate Wire AI (copy-paste, verified against @wireai/activation 0.13.5 + wireai-rn 0.2.4) Written for code-generating models. Every line is checked against the published package. Install both packages: ``` npm install @wireai/activation wireai-rn ``` Peer dependencies. Required: `react` >=18, `react-native` >=0.73, `react-native-safe-area-context` >=4 (host must render a `SafeAreaProvider` ancestor), `zod` >=3 (v3 only), `wireai-rn` >=0.2.3 (the renderer, installed above). Optional (guarded lazy require, no-op fallback when absent): `react-native-reanimated` >=3, `expo-blur` >=13, `expo-store-review` >=6, `expo-haptics` >=13, `@expo/vector-icons` >=14, `@blazejkustra/react-native-onboarding` >=1. No native rebuild is required for the kit itself (it is pure JS); a rebuild is only needed if the app's own gating mechanism uses a native module. Wire Metro. This is required: it keeps a single React copy, and a second React instance crashes React Native. ```js // metro.config.js const { getDefaultConfig } = require("expo/metro-config"); const { withWireOnboarding } = require("@wireai/activation/metro"); module.exports = withWireOnboarding(getDefaultConfig(__dirname)); ``` Set the env vars. Two are required: ``` EXPO_PUBLIC_WIREAI_API_KEY=wai_... EXPO_PUBLIC_WIREAI_SERVER_URL=https://... ``` `EXPO_PUBLIC_WIREAI_APP_ID` is optional (defaults to `"default"` if unset, or pass `appId` directly to `wireConfigFromEnv({ appId: "my-app" })`). The `apiKey` is a tenant key and is safe in the bundle. Get both required values at https://getwireai.com/signup. With an account the key stays readable in your console. While account signup is switched off the page returns it on the spot instead, once, with nothing able to return it a second time, so copy it when you see it. Render. `wireConfigFromEnv` returns `null` when env vars are missing, so keep a fallback: ```tsx import { WireOnboarding, themeFromBrand, wireConfigFromEnv } from "@wireai/activation"; export function Onboarding() { const config = wireConfigFromEnv({ appId: "my-app" }); if (!config) return null; // or render your static onboarding return ( { // result: { answers, raw } }} /> ); } ``` Required props: `config` and `onComplete` only. `config` must carry `apiKey`, `serverUrl`, `appId`. The join key. `user_context.device_key` is the only thing that joins an onboarding session to everything the app reports later; skipping it leaves the `activated` funnel permanently empty and reports no error. Since 0.12.2 the kit supplies its own, so the default is correct: - Pass `storage` to `` and the kit merges in its own persisted per-install key. - A host-supplied key always wins: `userContext={activationJoinContext(deviceKey)}` is forwarded verbatim, nothing merged over it. For a host that owns no device id, `activationJoinContext(resolveAutoDeviceKey({ appId, storage }))`. Never hand-write `userContext={{ deviceKey }}`: the server's device lookup reads `device_key`, and `activationJoinContext` is what produces that shape. - **No `storage`, no injection, and since 0.13.0 a `storage` adapter that FAILS earns no injection either.** The gate used to be a bare presence check on the `storage` prop, never the success of the write, so a rejecting adapter (locked, full, permission denied) injected a fresh `wdev_*` on every launch. That is worse than injecting nothing: the server counts `min_sessions` by distinct opens grouped on `device_key`, so a per-launch key makes that counter structurally incapable of exceeding 1 and inflates distinct-device counts. The gate now reads `IdentityRecord.durable`, true only when the id was adopted from storage or written to it successfully, and the dev warning names which of the three reasons applied. - **A host that owns its own device id must pass the SAME string on every surface**: `createAnalytics({ serverUrl, apiKey, appId, userContext: { deviceKey } })`, `useLifecycleEvents(config, { deviceKey })`, and `` (plus `createWireActivation({ deviceKey })` if you use it). Miss one surface and the kit injects its own key there, so the app's events and the onboarding session sit in disjoint id spaces and `activated` still reads zero. Since 0.13.0 every surface constructed with a host-supplied device key records it on a process-wide provenance registry scoped per `appId`, and a mount that injected its own key while the process demonstrably owns a host one warns in dev and prints both ids. It warns rather than adopting the other key: construction order cannot decide which of two ids a host meant. - `autoJoinKey={false}` opts out and restores the pre-0.12.2 behaviour, for a host that genuinely wants an unlinked onboarding session. Default `true`. - The join decision is FROZEN at the moment the loader gate opens (0.13.0). A host resolving its device key asynchronously used to render once without it, get `wdev_*` injected, then have the real key land a tick later, which rebuilt the A2A adapter, dropped the server-learned session id, and restarted the user's onboarding mid-flow. Session-start metadata now reads a frozen snapshot; client events keep reading the live context, so late host context still enriches them. Identity provenance (new in 0.13.0, root exports). Every id now carries a record instead of being a bare string. `resolveIdentity(...)` returns an `IdentityRecord` of `{ value, space, source, durable }`, where `space` is `"onboarding-session" | "app-session" | "device"`, `source` is `"host" | "auto"`, and `durable` says the value was actually persisted rather than living only in this process. `hostIdentity(space, appId)` answers the question no single mount can answer alone: does this app own a host-supplied id in that space that this mount was not given. `hydrateDeviceIdentity(...)` is the awaited, provenance-carrying sibling of `hydrateAutoDeviceKey`, which is unchanged and still returns a bare string. Most integrations never call these directly; they are the reason the kit now declines and says so where it used to fabricate an id and return `true`. Mount the lifecycle hook. Do not skip this step: ```tsx import { useLifecycleEvents } from "@wireai/activation"; useLifecycleEvents(config, { deviceKey, sessionCount, userId }); // once, at the app root ``` It emits `app.first_open` (once ever) and `app.session_started` (per open), it registers the per-open session id that `createAnalytics`, `wire.track` and the gates all correlate to, and with `config.storage` it stamps the persisted `device_key` on `app.session_started`, which is what the server's `min_sessions` rule counts. Mount it at the app root before anything else touches analytics. Alternatives, all root exports: `wireLifecycleEvents` is the React-free counterpart and fires both events in one call, `reportFirstOpen` fires just the once-ever one, and `useSessionStart` / `reportSessionStart` cover a host that already owns an open counter but do NOT emit `first_open`. `wireConfigFromEnv` returns `null` when a required env var is missing and warns once in dev naming which one, since a missing var otherwise produces three independent silent no-ops. `WIRE_ENV_VARS` is exported so a host preflight can assert the three names itself. Use the installed version's `INTEGRATION_PROMPT.md` as the API authority. The supported starter builds config with `wireConfigFromEnv` and renders ``; do not substitute a backend-example initializer. Subpath exports: `.`, `./analytics`, `./coachmarks`, `./showcase`, `./reviews`, `./questionnaire`, `./metro`. The analytics facade lives on `./analytics` only; importing it from the root does not compile. Analytics API (from `@wireai/activation/analytics`): - `createAnalytics({ serverUrl, apiKey, appId?, storage?, userContext? })` returns an offline-first `Analytics` façade with: - `track(event: string, props?: AnalyticsProps): void`, `screen(name: string, props?: AnalyticsProps): void` - `identify(userId: string, traits?: AnalyticsProps): void` binds the host's opaque user id and emits an identify event. - `setUserContext(partial: Partial): void` merges over the current context (`extra` is deep-merged); a supplied `userId` also binds like `identify`. - `flush(): void` - `reportAppEvent(target, name: string, options?): void` is a standalone helper (not a facade method) that POSTs directly to `{serverUrl}/v1/events`. - `resetEventQueueKeys()` is test-only: it clears the claimed-queue-key registry. A real relaunch is a new process; an in-process test that simulates one needs this or its second queue reads as a concurrent sibling. What changed in 0.13.0 that generated code must account for: - `identifyOnboarding` no longer binds to the app-open session by default, and its return type widened from `boolean` to `"onboarding" | "app_session" | false`. The old tier-3 fallback posted the per-open app session id into a field that means the ONBOARDING session, then returned `true`, so the row could never join the onboarding funnel while the host was told it had worked, and it fired in exactly the documented post-completion case. The fallback is now opt-in via `allowAppSessionFallback: true` and names itself in the return value. `"onboarding"` is truthy, so `if (await identifyOnboarding(...))` behaves identically at runtime; only an explicit `: boolean` annotation needs updating. - `wire.track()` returns `false` for an event the server DISCARDED. `POST {serverUrl}/v1/events` answers HTTP 200 with `{ ok, written, skipped }` and counts a refused event in `skipped`; the transport used to read `res.ok` alone and resolve `true` for an event that was thrown away, then bump decision revalidation so every subscribed gate re-fetched against a stream the action never entered. Only an explicit positive `skipped` demotes a 200, so an older server that sends no such field still resolves `true`. Do not treat `false` as a network error. - `OnboardingProgress.slot_id` is a stable per-slot identity for a screen. `readProgress` reads it and `deriveAnswers` prefers it over `key`; `validators` are looked up by `slot_id` first, then `progress.key`, so a validator map written against today's keys keeps working. The fallback is decided PER CARD and never cached as a per-session verdict, so a thread that mixes slotted and unslotted cards keys each one correctly. It matters because `key` is authored from the question's prompt text (tenant flows slugify it and cut at 32 chars), so re-wording a question mints a new key and a host reading `answers.interests` silently starts reading `undefined`. Version caveat: servers running funnel releases older than 2026-07-28, self-hosted ones included, never send `slot_id` at all, and `deriveAnswers` falls back to `progress.key` per card, so always keep the key-based map alongside any slot-keyed entries. - Two `createAnalytics` instances for one `appId` no longer share one persisted queue slot. The default storage key was derived from `appId` alone, so two instances shared one persisted backlog while keeping separate in-memory buffers: each persist overwrote the other's blob, a queue draining to empty deleted a sibling's still-pending events, and on relaunch the survivors were sent twice. The second claimant of a DEFAULT key now gets a rotated key and a dev warning. An explicit `storageKey` is never rotated, since that is a host declaring which slot it owns. - A failed device-key hydration is retried instead of cached for the process lifetime, so a transient cold-boot storage lock is no longer permanent. - The DISCARDED dev warning now covers all four send paths and reports what the server actually said (`skipped`, `written`, and any per-event `reasons`) instead of naming a cause the kit never checked. - Removed: `DoneBlock` (self-deprecated since 0.1.x, superseded by `CompletionView`) and `RESERVED_USER_CONTEXT_KEYS` (read by nothing; `EXTRA_KEY_PREFIX` is what actually enforces namespacing). Further pitfalls, pre-answered: - A `storage` adapter that throws or rejects: since 0.13.0 the kit declines the join key rather than injecting a per-launch one, and says why in dev. Verify your adapter actually writes before you assume the funnel is wired. - EAS and cloud builds: install from the registry or a git URL, never a local `file:` path outside the app repo. EAS cannot resolve it. - `zod` must be `^3`. Zod v4 changed the API and the SDK validates against v3. - Streaming uses XHR, not `fetch` streams, because Hermes does not expose fetch streaming. Hermes and the New Architecture are both supported. - `wireai-rn` is pure JS/TS with no native modules, so there is nothing to autolink for the renderer itself. ## Core pages - [Wire AI](https://getwireai.com/): The AI Growth Engineer for Mobile Apps. Five journey stages, four of them running four pre-generated versions at once, plus the AI personalization engine step. - [Create an account or get a key](https://getwireai.com/signup): the self-serve door. Account signup confirms your email, takes a password, and leaves the key readable in your console. While it is switched off the page hands you a key on the spot instead, once, creating nothing you can log into. No card, no call, either way. - [Pricing](https://getwireai.com/pricing): the founding cohort free (15 seats, 4 filled), analytics free to 299k events a month forever, then Free at €0, Starter at €49 a month, or Pro at €149, with no usage meter on experiments. Billing is not switched on yet, so nothing charges. - [How it works](https://getwireai.com/onboarding): Demo gallery, integration detail, FAQ. - [Open source](https://getwireai.com/generative-ui-react-native): Wire RN generative UI SDK. - [How to integrate](https://getwireai.com/integrate): Let your coding agent wire it, install it manually, or ship it with us. - [Documentation](https://getwireai.com/generative-ui-react-native/docs) - [Blog](https://getwireai.com/blog) ## Concepts (glossary) - [Generative UI for Mobile](https://getwireai.com/glossary/generative-ui-for-mobile) - [A2UI Protocol](https://getwireai.com/glossary/a2ui-protocol) - [Dynamic Onboarding](https://getwireai.com/glossary/dynamic-onboarding) - [Streaming UI](https://getwireai.com/glossary/streaming-ui) ## Onboarding examples - [Food Recommendation](https://getwireai.com/onboarding/food-recommendation) - [Mental Health](https://getwireai.com/onboarding/mental-health) ## Comparisons - [vs Crayon](https://getwireai.com/vs/crayon) - [vs LangChain UI](https://getwireai.com/vs/langchain-ui) - [vs Tambo](https://getwireai.com/vs/tambo) ## Policy and terms - [What Wire will not optimize on](https://getwireai.com/docs/what-wire-will-not-optimize-on): The three vulnerability classes Wire AI will not use as optimization inputs, and what customers may not instruct it to do: minor status and age, health condition, and financial distress. - [Terms and Conditions](https://getwireai.com/tos): The terms of service, including the Acceptable Use section listing the Annex III high-risk contexts Wire AI is not to be deployed in, and the Roles section (the customer is the deployer, Wire AI is the provider of a limited-risk system). - [Trust](https://getwireai.com/trust): Wire AI's role under the EU AI Act (provider of a limited-risk system, the customer is the deployer), the three signals it will not optimize on, its Article 25(2) specification that it is not to be changed into a high-risk system, its reasoned Article 50 transparency position pending legal review, and where the service runs. A data processing agreement and a current list of sub-processors are available on request. ## Contact - Email: malik@getwireai.com