Prepare an OpenIAP implementation brief and check local purchase configuration.
Use init to choose a starting guide for your coding assistant; use doctor
to catch known configuration mistakes before a build or after an AI edit.
Requires Node.js 20 or later.
npx @hyodotdev/openiap # choose a role in your terminal
npx @hyodotdev/openiap init ./my-product --role experience
npx @hyodotdev/openiap doctor ./my-app --jsonThe project directory must already exist. init prints Markdown to stdout;
it does not create a project or a file. Without arguments, an interactive
terminal opens the role picker; a non-interactive run prints help.
| Role | Connect |
|---|---|
app |
Purchases to customer access |
experience |
Paywalls and experiments to the app's purchase flow |
commerce |
Verification and access through the Commerce Protocol |
data |
Normalized events to analytics and automation |
The brief points your assistant to the matching implementation guide. Fill in the customer outcome, choose any missing product decisions, and review the running result. Run again for each role your product supplies.
For scripts and coding agents, select a role explicitly with --role. To save
the output, redirect it yourself. This creates or replaces openiap-brief.md:
npx @hyodotdev/openiap init --role commerce > openiap-brief.mdThe CLI itself only reads local files. It does not install SDKs, execute app
configuration, fetch the linked guides, contact a service, or invoke an AI.
npx may download the CLI and its dependencies before running it.
From your existing app directory, run npx @hyodotdev/openiap init --role app.
This works even when the app already uses OpenIAP. It prints a starting brief;
it does not analyze or migrate the existing purchase flow.
When the command exits, copy the full document beginning with
# OpenIAP implementation brief. Open the same project in your coding assistant
(for example, Codex or Claude Code) and paste it into the assistant's chat
input. Append your desired outcome below it, starting with
Desired outcome:, such as adding Premium while keeping your current login,
paywall, and purchase integration. Send both as one message. The AI then inspects and edits the project, runs it, and reports tests.
At the end of init alone, your files are unchanged and no AI or server is running.
Follow the AI handoff, then inspect the connection example and its evidence.
init is an optional shortcut. It adds the project path, selected role, and a
framework hint to a brief that links the maintained guide. It does not analyze
your purchase code, select a backend, or check the implementation. If your
assistant already has the relevant guide and project context, skip init.
doctor supplies repeatable checks with stable finding IDs, file locations,
suggested fixes, JSON output, and an error exit code. An assistant can inspect
the same files itself; running the CLI makes these particular checks consistent
across developers, agents, and CI. Review a finding, fix its cause, and rerun
the same command. Pin the CLI version in CI to keep the rule set consistent.
| Need | Use |
|---|---|
| Decide where an app, paywall, backend, or data service connects | init --role …, or the role guide directly |
| Catch supported local configuration mistakes | doctor --json in the target app directory |
| See verification, ownership, access, and delivery execute | The runnable Commerce Protocol example |
| Verify a provider's protocol behavior | The conformance tools and tests for its declared profiles |
The CLI is not needed to run the example or use OpenIAP SDKs. The example is
fixture-backed teaching code; neither its tests nor a clean doctor report
prove that real store purchases work.
Run doctor at the target app root, not the monorepo root. It does not
recursively discover apps. Framework hints recognize Expo, React Native,
Flutter, and KMP dependency declarations; other stacks report unknown.
The four init roles select guides, not four sets of diagnostic checks.
Android checks inspect conventional android/ Gradle and manifest files.
iOS scene checks apply to Expo and React Native ios/ projects. IAPKit key and
URL checks read env and app configuration files, including relevant Flutter
assets. They are not a general source-code or secret scan. A custom layout or
missing native directory can leave a check skipped; inspect
notCheckedLocally even when the command exits 0.
Error means the checkout proves it: two files disagree, or a value is wrong
for its documented use. Errors exit 1.
Warning means the checkout suggests it but cannot settle it — Gradle can
inject a manifest placeholder, a linked framework can supply a class, and no
file records whether the app reads a given variable. Warnings exit 0.
A build for another store is settled but deliberate, so it is a warning too: the tool cannot know which device you are about to install on.
Both means the level depends on what the checkout shows. A malformed base URL is an error where something inlines the name it is assigned to, and a warning where nothing does; a missing scene delegate is an error when the Info.plist names the app's own module or no class at all, and a warning when the name could come from a linked framework.
Most of these produce no error message that says what is actually wrong.
| Check | Level | What goes wrong without it |
|---|---|---|
android-store-flavor-mismatch |
error | A half-finished regeneration links one store while gradle.properties pins another. |
android-store-flavor-conflict |
error | The openiapStore pin and a store flag disagree, or horizonEnabled and fireOsEnabled are both true. Gradle also refuses this; the doctor sees it before a build. |
android-store-unknown |
error | openiapStore names something that is not a store, so Gradle will refuse the build. |
android-store-not-play |
warning | The project is pinned to Horizon, Amazon, or no store at all, so Play billing cannot connect on a Play device. Without a pin the task flavor or the connected debug device picks the store per build. |
android-horizon-app-id-missing |
warning | Horizon is pinned but no manifest declares an app id. |
iapkit-secret-key-in-client |
error | A secret key is on a name that reaches the app bundle. |
iapkit-secret-key-in-env |
warning | A secret key is in an env file on a name nothing here proves is inlined. |
iapkit-secret-key-in-config |
warning | Executable app configuration contains a secret, but its presence in the app bundle is unproven. |
iapkit-env-missing-expo-prefix |
warning | Expo inlines only EXPO_PUBLIC_ names, so the bare name reads as undefined. |
iapkit-env-unexpected-expo-prefix |
warning | An EXPO_PUBLIC_ name is set where nothing inlines that prefix. |
iapkit-base-url-has-path |
both | The base URL is not a bare origin; every SDK rejects a path, userinfo, a query or a fragment. |
iapkit-base-url-invalid |
both | The base URL is not a URL. |
iapkit-base-url-scheme |
both | The base URL is not http or https. |
ios-scene-delegate-missing |
both | The Info.plist names a scene delegate the target lacks; the app opens to a black screen. Error when the plist names the app's own module or no class at all, warning when a linked framework could supply it. |
project-file-unreadable |
error | A path could not be read, so nothing in it was checked. |
project-manifest-unreadable |
error | package.json exists but will not parse, so framework detection read nothing. |
project-not-a-directory |
error | The path given is not a readable directory. |
Dynamic app configuration is never executed. Secret literals in it stay warnings unless the file is a declared bundled asset. Reading an unprefixed environment variable during configuration also does not prove its value reaches the app.
A checkout cannot answer for a device or a store account. The command prints these as unchecked rather than guessing:
- Store account state: agreements, product status, and license testers.
- Device state: a scene session or an installed build left by another app that shares the bundle id.
- Play billing availability on the device and its signed-in account.
npx @hyodotdev/openiap doctor # the working directory
npx @hyodotdev/openiap doctor ./my-app # a project elsewhere
npx @hyodotdev/openiap doctor --json # one JSON report
npx @hyodotdev/openiap --version # the version and nothing elseExit code is 1 when there is an error, 0 otherwise.
--json emits {framework, findings, errors, warnings, notCheckedLocally}.
Each finding carries a stable id, a level, the file it was read from, a
message, a fix, and — where the check can point at one — a line,
expected, and actual. Match on id; the prose is for people.
List your app on the OpenIAP showcase.
Questions and feedback go to Discussions.
If @hyodotdev/openiap saves you time, a ⭐ on hyodotdev/openiap helps.
Thank you to Meta and Amazon Developer for supporting OpenIAP. View sponsorship options.
We also recognize sponsors and backers through OpenCollective. The original react-native-iap collective now supports the broader OpenIAP ecosystem and is managed separately from the main sponsor program.
Become a sponsor | Become a backer
Supported the project before the OpenIAP sponsor program.