CLI & MCP
@botsi/cli is a command-line tool (botsi) and a local MCP server. Use it to manage an app's access levels, products, paywalls, placements and experiments, and to check whether its integration works. It runs against one app at a time, using that app's secret key and the Botsi management API (/v2/manage/*).
You need Node.js 20 or later on macOS, Linux or Windows, a Botsi app, and an Owner or Admin to turn on one switch in the dashboard.
Quick start#
npx @botsi/cli initinit asks for the app's secret key (input is hidden), checks it, and shows the app it belongs to:
✓ Key works: <App name> (<app id>)It then saves the key under a profile name, asks which coding agent you use, and offers to write its MCP config:
- Claude Code:
.mcp.jsonin the current folder. - Codex:
~/.codex/config.toml(or$CODEX_HOME/config.toml).
The config names the profile. The key is never written into it.
When it finishes, open Claude Code in that folder and run /mcp to see the Botsi server connected. Then start the integration:
/mcp__botsi-<profile>__integrateIn Codex, paste the output of this command instead:
botsi integrate --print-promptIf something is wrong (a wrong key, the management API switched off, the app not enabled for the Botsi API, a network error), init stops and says how to fix it. To target another API host (default https://api.botsi.com), pass --base-url:
npx @botsi/cli init --base-url https://api.example.comIn a script, skip the prompts and run these instead:
botsi profiles add my-app-prod < key.txt
botsi agent setup --client claude --profile my-app-prod --write # or --client codexBefore you start#
- Turn on the management API. An Owner or Admin opens App Settings › General in the dashboard and turns on Management API. It's off by default, and every call answers
403 management_api_disableduntil it's on. - Copy the app's secret key from the same page.
- Pick the app. Every command acts on the one app its key belongs to. Publishing a placement or starting an experiment changes what that app's users see. The tools work on production apps, so consider trying changes on a separate test app first.
Keep the secret key off devices and out of git
The secret key can do everything the management API can for its app. Keep it on your machine and your servers only. Mobile and web clients use the app's public key.
Connecting your coding agent#
init does this for you. To do it later, or for another app:
botsi agent setup --client claude --profile my-app-prod --write # adds the server to ./.mcp.json
botsi agent setup --client codex --profile my-app-prod --write # appends to ~/.codex/config.tomlWithout --write it only prints the config. It also prints a short snippet to add to CLAUDE.md or AGENTS.md so the agent knows how to use Botsi in that repository.
To set it up by hand:
claude mcp add botsi-my-app-prod -- npx -y @botsi/cli mcp --profile my-app-prod{
"mcpServers": {
"botsi-my-app-prod": {
"command": "npx",
"args": ["-y", "@botsi/cli", "mcp", "--profile", "my-app-prod"]
}
}
}Cursor reads this from .cursor/mcp.json. Register one server per app, named botsi-<profile>, so an agent working on one app can't drift to another.
On Windows, wrap npx in cmd /c
Outside WSL, npx is a .cmd script that agents can't start directly. init and agent setup handle this for you. If you set it up by hand, use these forms instead.
Claude Code:
claude mcp add botsi-my-app-prod -- cmd /c npx -y @botsi/cli mcp --profile my-app-prodCursor:
{
"mcpServers": {
"botsi-my-app-prod": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@botsi/cli", "mcp", "--profile", "my-app-prod"]
}
}
}Having an agent integrate Botsi#
The MCP server's integrate prompt has the agent integrate Botsi end to end. Agents without MCP prompts, such as Codex, use botsi integrate --print-prompt. Add --platform ios|android|flutter|react-native|web|server to narrow it.
The agent works in nine phases and stops for your go-ahead after each one. It never runs a destructive action without asking.
The agent follows a built-in guide for each platform: iOS (Swift, URLSession, StoreKit 2), Android (Kotlin, Google Play Billing), Flutter (Dart, in_app_purchase), React Native (fetch, for example with react-native-iap), web (browser fetch with the public key, sales recorded from your server) and server (a custom webhook for sales no store reports, plus store server notifications). Read one with botsi guide ios.
Running experiments#
There are two kinds, and you choose. The agent asks rather than picking for you.
botsi experiments create --name "Pricing model" --type dynamic --goal-metric rpi \
--baseline-paywall-id 11 --model-traffic-bps 5000 --candidate-paywall-ids 12,13 \
--placement-plan-id 42A Static A/B test's arms is a list of objects, so put it in a JSON file and pass it with --file.
A new experiment appears in the dashboard's placement editor right away, added to its placement's draft at 0% traffic. Creating it changes no traffic, and deleting a draft experiment removes that entry.
Starting an experiment doesn't send it traffic
After start, save the placement draft with a weight for the experiment (all weights total 10000), validate it, then publish.
Botsi staff enable experiments per app. Until then, botsi app shows experimentsEnabled: false: you can create and validate experiments, but starting one is refused with 422 inference_settings_required. Ask Botsi to enable experiments for your app.
A started experiment is fixed. To change its configuration, duplicate it into a new draft. Changing a live experiment's arm allocation is a separate switch Botsi turns on (allocationChangeEnabled).
Checking your integration#
botsi health # the last 24 hours
botsi health --since 2026-11-04T10:00:00Z # a window up to 30 days back
botsi health --profile-id 0072102a-c00c-4ea5-9271-1b6e975f2d63 # trace one user end to endAgents use the health_get tool. The report has an overall result (pass, warn or fail) and a list of checks. Each check is pass, warn, fail or not_applicable, with a hint saying what it saw and what usually fixes it.
With --profile-id, a trace follows one user: profile_created → paywall_decided → paywall_shown → purchase_validated → purchase_bound → revenue_recorded. Sandbox purchases are never bound to a paywall session, by design. When request logs are available for the app, the report also lists failed requests (route, status, error code).
botsi health exits 1 when the report fails, so CI can gate on it. Reports can lag by about a minute, so re-check before changing code that looks right.
Using the CLI directly#
Managing profiles#
A profile is one app's secret key saved under a name. The key is checked against the API before it's saved.
botsi profiles add my-app-prod # prompts for the key, hidden
botsi profiles add my-app-test < key.txt # or reads it from stdin
botsi profiles list # shows the last four characters of each key
botsi profiles use my-app-test # makes it the default
botsi profiles remove my-app-prodThe CLI never accepts a key as a command-line argument, since it would land in your shell history. Profiles live in $XDG_CONFIG_HOME/botsi/config.json (default ~/.config/botsi/config.json), readable by you only.
A command uses the first of these it finds: --profile <name>, BOTSI_PROFILE, BOTSI_SECRET_KEY (meant for CI, with BOTSI_BASE_URL, default https://api.botsi.com), then the default profile.
Running commands#
Every command lists its subcommands and flags with --help:
botsi experiments --help- Output: every command prints
App: <name> (<id>)to stderr, then the response as JSON on stdout, so it pipes cleanly intojq. - Request bodies: pass
--file <path>(or--file -for stdin), plus a flag for each plain field, such as--revisionor--placement-key. A flag wins over the same field in the file. Lists of ids are one comma-separated flag (--candidate-paywall-ids 12,13). - Retries: creates and lifecycle actions carry an idempotency key. If you don't pass
--idempotency-key, the CLI makes one and prints it. Rerun with it to retry without acting twice. - Revisions: drafts carry a
revision. Send the current one with saves and lifecycle actions. A stale one answers409 revision_conflict. - Confirmation: destructive commands ask first, naming the app. In scripts, pass
--yes. Without a terminal and without--yes, they refuse. - Exit codes:
0on success,1when the API refused (its code and details are printed) or the request failed,2on a usage error.
The key you give an access level (--access-level-id, for example pro) is what your app checks for the entitlement. An access level can be renamed or deleted only while no product uses it and no customer has it. The default access level is never deleted.
MCP reference#
botsi mcp [--profile <name>] serves one app over stdio until the agent disconnects. Every tool result starts with App: <name> (<id>). If the app can't be read at startup (a wrong key, the switch off), the server still runs and each tool answers with the reason. Fixing it takes effect on the next call.
Each tool maps to a CLI command: x_y is botsi x y. Read tools change nothing. Write tools change data with no live effect. Destructive tools delete, or change live traffic or experiment state.
Read tools are marked read-only, and destructive tools carry destructiveHint. These are hints to your MCP client, which decides whether to ask you before a call.
An MCP client set to auto-approve tools runs destructive tools without asking
That includes publishing placements and starting experiments. Leave confirmation on for Botsi's destructive tools.
The server also exposes two kinds of resources and one prompt:
botsi://openapi/web-api: the Botsi API's OpenAPI document, with runtime and management routes and their schemas.botsi://guides/<platform>: the six integration guides.integrate: the integration prompt, with an optionalplatformargument.
Security#
- The key stays in your profile file, readable by you only. Agent configs name the profile, never the key.
- The CLI masks keys in output to their last four characters and never prints them in full.
- The management API never returns keys, key hashes, notification keys, or webhook and store settings.
- Destructive actions ask first in the CLI. In MCP clients they're marked so your client can ask.
What it doesn't do#
- Create apps, list your other apps, show keys, or change webhook or store settings. Use the dashboard.
- Start experiments before Botsi enables them for the app, or change a live allocation before Botsi turns that on.